Ticket #16796: GpxColors-v0.16.diff

File GpxColors-v0.16.diff, 159.6 KB (added by Bjoeni, 7 years ago)
  • data/gpx-drawing-extensions-1.0.xsd

     
     1<?xml version="1.0" encoding="UTF-8"?>
     2<schema targetNamespace="https://josm.openstreetmap.de/gpx-drawing-extensions-1.0"
     3        elementFormDefault="qualified"
     4        xmlns="http://www.w3.org/2001/XMLSchema"
     5        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     6        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     7        xmlns:gpxd="https://josm.openstreetmap.de/gpx-drawing-extensions-1.0"
     8        xsi:schemaLocation="https://josm.openstreetmap.de/gpx-drawing-extensions-1.0 https://josm.openstreetmap.de/gpx-drawing-extensions-1.0.xsd">
     9
     10        <xsd:annotation>
     11                <xsd:documentation>
     12                        This schema defines drawing extensions for the GPX 1.1 schema (http://www.topografix.com/GPX/1/1/gpx.xsd).
     13                        Elements in this schema should be used as child elements of the "extensions" element defined by the GPX schema.
     14                </xsd:documentation>
     15        </xsd:annotation>
     16       
     17        <!-- Elements -->
     18
     19        <xsd:element name="color" type="gpxd:hexColor_type">
     20                <xsd:annotation>
     21                        <xsd:documentation>
     22                                The color of the element, i.e. #RRGGBB or #RRGGBBAA.
     23                                Note that applications should apply possible alpha values to the lines and opacity to the whole track. This means that overlapping parts of the
     24                                track with alpha values will look more intense than individual lines, whereas the opacity affects the whole track including overlapping parts.
     25                        </xsd:documentation>
     26                </xsd:annotation>
     27        </xsd:element>
     28       
     29        <xsd:element name="opacity" type="gpxd:opacity_type">
     30                <xsd:annotation>
     31                        <xsd:documentation>
     32                                The opacity of the element between 0.00 and 1.00.
     33                        </xsd:documentation>
     34                </xsd:annotation>
     35        </xsd:element>
     36       
     37        <xsd:element name="width" type="xsd:positiveInteger">
     38                <xsd:annotation>
     39                        <xsd:documentation>
     40                                The width of the line in pixels, applications may use a width relative to this value if required.
     41                        </xsd:documentation>
     42                </xsd:annotation>
     43        </xsd:element>
     44       
     45        <xsd:element name="dashPattern" type="gpxd:dashPattern_type">
     46                <xsd:annotation>
     47                        <xsd:documentation>
     48                                The dash pattern of the line, see gpxd:dashPattern_type. Should always be relative to the width.
     49                        </xsd:documentation>
     50                </xsd:annotation>
     51        </xsd:element>
     52       
     53        <!-- Types -->
     54
     55        <xsd:simpleType name="hexColor_type">
     56                <xsd:annotation>
     57                        <xsd:documentation>
     58                                The hexColor_type must be a # followed by a 6 or 8-digit hex representation of the color (with or without the alpha value).                     
     59                        </xsd:documentation>
     60                </xsd:annotation>
     61                <xsd:restriction base="xsd:string">
     62                        <xsd:pattern value="\#([a-fA-F0-9]{6}|[a-fA-F0-9]{8})" />
     63                        <xsd:whiteSpace value="collapse" />
     64                </xsd:restriction>
     65        </xsd:simpleType>
     66
     67        <xsd:simpleType name="opacity_type">
     68                <xsd:annotation>
     69                        <xsd:documentation>
     70                                The opacity_type must be a decimal value between 0 and 1.
     71                        </xsd:documentation>
     72                </xsd:annotation>
     73                <xsd:restriction base="xsd:decimal">
     74                        <xsd:minInclusive value="0" />
     75                        <xsd:maxInclusive value="1" />
     76                </xsd:restriction>
     77        </xsd:simpleType>
     78       
     79        <xsd:simpleType name="dashPattern_type">
     80                <xsd:annotation>
     81                        <xsd:documentation>
     82                                The dashPattern_type can be
     83                                        - a representation of the pattern as y-n-y-n-... with y being the relative length of the line that is
     84                                          visible and n being the relative length of the line that is hidden to create a dashed / dotted line.
     85                                          Has to have an even number of segments (at least two) and can contain multi-digit numbers.
     86                                        - one of the following predefined values:
     87                                          none, dash-long, dash-medium, dash-short, dot-sparse, dot-normal, dot-dense, dash-dot, dash-dot-dot
     88                        </xsd:documentation>
     89                </xsd:annotation>
     90                <xsd:restriction base="xsd:string">                             <!-- use string based pattern instead of enum because both pattern and enums are allowed -->
     91                        <xsd:pattern value="\d+\-\d+(\-\d+\-\d+)*" />   <!-- pattern, see documentation above -->
     92                        <xsd:pattern value="none" />                                    <!-- 1-0, default value/line -->
     93                        <xsd:pattern value="dash-long" />                               <!-- 6-2 -->
     94                        <xsd:pattern value="dash-medium" />                             <!-- 4-4 -->
     95                        <xsd:pattern value="dash-short" />                              <!-- 2-6 -->
     96                        <xsd:pattern value="dot-sparse" />                              <!-- 1-4 -->
     97                        <xsd:pattern value="dot-normal" />                              <!-- 1-2 -->
     98                        <xsd:pattern value="dot-dense" />                               <!-- 1-1 -->
     99                        <xsd:pattern value="dash-dot" />                                <!-- 4-2-1-2 -->
     100                        <xsd:pattern value="dash-dot-dot" />                    <!-- 4-2-1-2-1-2 -->                   
     101                </xsd:restriction>
     102        </xsd:simpleType>
     103       
     104</schema>
     105 No newline at end of file
  • data/gpx-extensions-1.1.xsd

     
     1<?xml version="1.0" encoding="UTF-8"?>
     2<schema targetNamespace="http://josm.openstreetmap.de/gpx-extensions-1.1"
     3        elementFormDefault="qualified"
     4        xmlns="http://www.w3.org/2001/XMLSchema"
     5        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     6        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     7        xmlns:josm="http://josm.openstreetmap.de/gpx-extensions-1.1"
     8        xsi:schemaLocation="http://josm.openstreetmap.de/gpx-extensions-1.1 http://josm.openstreetmap.de/gpx-extensions-1.1.xsd">
     9       
     10    <!-- true, if gpx data has been downloaded from the osm server -->
     11    <!-- it this case, JOSM improves the rendering of clouds of anonymous TrackPoints -->
     12    <element name="from-server" type="boolean"/>
     13   
     14    <!-- the following properties are only set for marker layer export -->
     15    <element name="offset" type="decimal"/>
     16    <element name="sync-offset" type="decimal"/>
     17    <element name="text" type="string" />
     18   
     19    <xsd:element name="layerPreferences" type="josm:preferences_type">
     20        <xsd:annotation>
     21                <xsd:documentation>
     22                        The layerPreferences contain the preferences that can be set for the layer, e.g. in the "Customize track drawing" dialog in JOSM.
     23                </xsd:documentation>
     24        </xsd:annotation>
     25    </xsd:element>
     26
     27        <xsd:complexType name="preferences_type">
     28                <xsd:sequence>
     29                        <xsd:element name="entry" type="josm:entry_type" minOccurs="0" />
     30                </xsd:sequence>
     31        </xsd:complexType>
     32
     33        <xsd:complexType name="entry_type">
     34                <xsd:attribute name="key" type="xsd:string" use="required" />
     35                <xsd:attribute name="value" type="xsd:string" use="required" />
     36        </xsd:complexType>
     37       
     38
     39</schema>
     40 No newline at end of file
  • src/org/openstreetmap/josm/actions/ShowStatusReportAction.java

     
    293293        text.append(reportHeader);
    294294
    295295        Preferences.main().getAllSettings().forEach((key, setting) -> {
    296             if (key.startsWith("marker.show")
    297                     || "file-open.history".equals(key)
     296            if ("file-open.history".equals(key)
    298297                    || "download.overpass.query".equals(key)
    299298                    || "download.overpass.queries".equals(key)
    300299                    || key.contains("username")
  • src/org/openstreetmap/josm/data/gpx/Extensions.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
    4 import java.util.LinkedHashMap;
     4import java.util.TreeMap;
    55
     6import org.apache.commons.jcs.access.exception.InvalidArgumentException;
     7
    68/**
    7  * Data class for extensions in a GPX-File.
     9 * Data class for extensions of a specific type in a GPX-File (keys are case-insensitive).
    810 */
    9 public class Extensions extends LinkedHashMap<String, String> {
     11public final class Extensions extends TreeMap<String, String> {
    1012
    1113    private static final long serialVersionUID = 1L;
     14    private String type;
    1215
    1316    /**
    1417     * Constructs a new {@code Extensions}.
     18     * @param extensionType type of the extension, can be any <code>GpxConstants.EXTENSIONS_*</code>
    1519     */
    16     public Extensions() {
    17         super();
     20    public Extensions(String extensionType) {
     21        super(String.CASE_INSENSITIVE_ORDER);
     22        if (!extensionType.startsWith(GpxConstants.EXTENSIONS_PREFIX))
     23            throw new InvalidExtensionException();
     24        type = extensionType;
    1825    }
     26
     27    /**
     28     * Returns the prefix for the XML namespace.
     29     * @return the prefix including the <code>:</code>
     30     */
     31    public String getPrefix() {
     32        return type.substring(type.lastIndexOf(".") + 1) + ":";
     33    }
     34
     35    /**
     36     * @return type of the extension
     37     */
     38    public String getType() {
     39        return type;
     40    }
     41
     42    /**
     43     * InvalidExtensionException is thrown if the Extension value does not match the expected format
     44     * @see GpxConstants#EXTENSIONS_PREFIX
     45     */
     46    public static class InvalidExtensionException extends InvalidArgumentException {
     47        /**
     48         * Constructs a new {@link InvalidExtensionException} with a default error message
     49         */
     50        public InvalidExtensionException() {
     51            super("The extensionType must start with the extensions prefix.");
     52        }
     53        /**
     54         * Constructs a new {@link InvalidExtensionException} with the given error message
     55         * @param message
     56         */
     57        public InvalidExtensionException(String message) {
     58            super(message);
     59        }
     60    }
     61
    1962}
  • src/org/openstreetmap/josm/data/gpx/GpxConstants.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
     4import java.awt.Color;
    45import java.util.Arrays;
    56import java.util.Collection;
    67import java.util.Collections;
    78import java.util.List;
     9import java.util.Map;
     10import java.util.TreeMap;
    811
    912import org.openstreetmap.josm.data.Bounds;
    1013import org.openstreetmap.josm.spi.preferences.Config;
     
    9598     * @see GpxData#getMetaBounds()
    9699     */
    97100    String META_BOUNDS = META_PREFIX + "bounds";
     101
    98102    /**
    99      * A constant for the metadata hash map: the extension data. This is a {@link Extensions} object
    100      * @see GpxData#addExtension(String, String)
     103     * Prefix used for all extension values.
     104     * Note that all extension values <b>must</b> end with the according XML namespace prefix (i.e <code>.josm</code> or <code>.gpxd</code>)
     105     */
     106    String EXTENSIONS_PREFIX = "extensions.";
     107
     108    /**
     109     * The JOSM extension data (josm:*). This is a {@link Extensions} object
     110     * @see GpxData#addExtensionKey(String, String, String)
    101111     * @see GpxData#get(String)
    102112     */
    103     String META_EXTENSIONS = META_PREFIX + "extensions";
     113    String EXTENSIONS_JOSM = EXTENSIONS_PREFIX + "josm";
    104114
    105115    /**
    106      * A namespace for josm GPX extensions
     116     * The GPX drawing extension data (gpxd:*). This is a {@link Extensions} object
    107117     */
    108     String JOSM_EXTENSIONS_NAMESPACE_URI = Config.getUrls().getXMLBase() + "/gpx-extensions-1.0";
     118    String EXTENSIONS_DRAWING = EXTENSIONS_PREFIX + "gpxd";
    109119
     120    /**
     121     * The Garmin GPX extension data (gpxx:*). This is a {@link Extensions} object
     122     */
     123    String EXTENSIONS_GARMIN = EXTENSIONS_PREFIX + "gpxx";
     124
     125    /**
     126     * Namespace for JOSM GPX extensions
     127     */
     128    String XML_URI_EXTENSIONS_JOSM = Config.getUrls().getXMLBase() + "/gpx-extensions-1.1";
     129    /**
     130     * Location of the XSD schema for JOSM GPX extensions
     131     */
     132    String XML_XSD_EXTENSIONS_JOSM = Config.getUrls().getXMLBase() + "/gpx-extensions-1.1.xsd";
     133
     134    /**
     135     * Namespace for GPX drawing extensions
     136     */
     137    String XML_URI_EXTENSIONS_DRAWING = Config.getUrls().getXMLBase() + "/gpx-drawing-extensions-1.0";
     138    /**
     139     * Location of the XSD schema for GPX drawing extensions
     140     */
     141    String XML_XSD_EXTENSIONS_DRAWING = Config.getUrls().getXMLBase() + "/gpx-drawing-extensions-1.0.xsd";
     142
     143    /**
     144     * Namespace for Garmin GPX extensions
     145     */
     146    String XML_URI_EXTENSIONS_GARMIN = "http://www.garmin.com/xmlschemas/GpxExtensions/v3";
     147    /**
     148     * Location of the XSD schema for GPX drawing extensions
     149     */
     150    String XML_XSD_EXTENSIONS_GARMIN = "http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd";
     151
    110152    /** Elevation (in meters) of the point. */
    111153    String PT_ELE = "ele";
    112154
     
    154196     */
    155197    List<String> WPT_KEYS = Collections.unmodifiableList(Arrays.asList(PT_ELE, PT_TIME, PT_MAGVAR, PT_GEOIDHEIGHT,
    156198            GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, PT_SYM, PT_TYPE,
    157             PT_FIX, PT_SAT, PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID, META_EXTENSIONS));
     199            PT_FIX, PT_SAT, PT_HDOP, PT_VDOP, PT_PDOP, PT_AGEOFDGPSDATA, PT_DGPSID,
     200            EXTENSIONS_JOSM, EXTENSIONS_DRAWING, EXTENSIONS_GARMIN));
    158201
    159202    /**
    160203     * Ordered list of all possible route and track keys.
    161204     */
    162205    List<String> RTE_TRK_KEYS = Collections.unmodifiableList(Arrays.asList(
    163             GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, "number", PT_TYPE, META_EXTENSIONS));
     206            GPX_NAME, GPX_CMT, GPX_DESC, GPX_SRC, META_LINKS, "number", PT_TYPE,
     207            EXTENSIONS_JOSM, EXTENSIONS_DRAWING, EXTENSIONS_GARMIN));
    164208
    165209    /**
     210     * Possible extension namespaces
     211     */
     212    List<String> SUPPORTED_EXTENSION_NAPMESPACES = Collections.unmodifiableList(Arrays.asList(
     213            "josm", "gpxx", "gpxd"));
     214
     215    /**
     216     * Map with all supported Garmin colors
     217     */
     218    Map<String, Color> GARMIN_COLORS = Collections.unmodifiableMap(
     219            new TreeMap<String, Color>(String.CASE_INSENSITIVE_ORDER) {{
     220                put("Black", Color.BLACK);
     221                put("DarkRed", new Color(139, 0, 0));
     222                put("DarkGreen", new Color(0, 100, 0));
     223                put("DarkYellow", new Color(255, 170, 0));
     224                put("DarkBlue", new Color(0, 0, 139));
     225                put("DarkMagenta", new Color(139, 0, 139));
     226                put("DarkCyan", new Color(0, 139, 139));
     227                put("LightGray", Color.LIGHT_GRAY);
     228                put("DarkGray", Color.DARK_GRAY);
     229                put("Red", Color.RED);
     230                put("Green", Color.GREEN);
     231                put("Yellow", Color.YELLOW);
     232                put("Blue", Color.BLUE);
     233                put("Magenta", Color.MAGENTA);
     234                put("Cyan", Color.CYAN);
     235                put("White", Color.WHITE);
     236                put("Transparent", new Color(0, 0, 0, 255));
     237            }});
     238
     239    /**
    166240     * Possible fix values. NMEA 0183 Version 4.00
    167241     */
    168242    Collection<String> FIX_VALUES = Collections.unmodifiableList(
  • src/org/openstreetmap/josm/data/gpx/GpxData.java

     
    5454    public String creator;
    5555
    5656    /**
     57     * The layer specific prefs formerly saved in the preferences, e.g. drawing options.
     58     * NOT the segment specific settings (e.g. color, width)
     59     */
     60    public Map<String, String> layerPrefs = new HashMap<String, String>()  {};
     61
     62    /**
    5763     * A list of tracks this file consists of
    5864     */
    5965    private final ArrayList<GpxTrack> privateTracks = new ArrayList<>();
     
    6571     * Addidionaly waypoints for this file.
    6672     */
    6773    private final ArrayList<WayPoint> privateWaypoints = new ArrayList<>();
    68     private final GpxTrackChangeListener proxy = e -> fireInvalidate();
     74    private final GpxTrackChangeListener proxy = e -> {fireInvalidate(); setModified();};
     75    private boolean modified = false;
    6976
    7077    /**
    7178     * Tracks. Access is discouraged, use {@link #getTracks()} to read.
     
    888895        private Line next;
    889896        private final boolean[] trackVisibility;
    890897        private Map<String, Object> trackAttributes;
     898        private GpxTrack curTrack;
    891899
    892900        /**
    893901         * Constructs a new {@code LinesIterator}.
     
    921929        private Line getNext() {
    922930            if (itTracks != null) {
    923931                if (itTrackSegments != null && itTrackSegments.hasNext()) {
    924                     return new Line(itTrackSegments.next(), trackAttributes);
     932                    return new Line(itTrackSegments.next(), trackAttributes, curTrack.getColor());
    925933                } else {
    926934                    while (itTracks.hasNext()) {
    927                         GpxTrack nxtTrack = itTracks.next();
    928                         trackAttributes = nxtTrack.getAttributes();
     935                        curTrack = itTracks.next();
     936                        trackAttributes = curTrack.getAttributes();
    929937                        idxTracks++;
    930938                        if (trackVisibility != null && !trackVisibility[idxTracks])
    931939                            continue;
    932                         itTrackSegments = nxtTrack.getSegments().iterator();
     940                        itTrackSegments = curTrack.getSegments().iterator();
    933941                        if (itTrackSegments.hasNext()) {
    934                             return new Line(itTrackSegments.next(), trackAttributes);
     942                            return new Line(itTrackSegments.next(), trackAttributes, curTrack.getColor());
    935943                        }
    936944                    }
    937945                    // if we get here, all the Tracks are finished; Continue with Routes
     
    981989                return false;
    982990        } else if (!dataSources.equals(other.dataSources))
    983991            return false;
     992        if (layerPrefs == null) {
     993            if (other.layerPrefs != null)
     994                return false;
     995        } else if (!layerPrefs.equals(other.layerPrefs))
     996            return false;
    984997        if (privateRoutes == null) {
    985998            if (other.privateRoutes != null)
    986999                return false;
     
    10671080            return source;
    10681081        }
    10691082    }
     1083
     1084    /**
     1085     * @return whether anything (i.e. colors) have been modified
     1086     */
     1087    public boolean isModified() {
     1088        return modified;
     1089    }
     1090
     1091    /**
     1092     * Sets the modified flag to true.
     1093     */
     1094    public void setModified() {
     1095        setModified(true);
     1096    }
     1097
     1098    /**
     1099     * Sets the modified flag to the value.
     1100     * @param value
     1101     */
     1102    public void setModified(boolean value) {
     1103        modified = value;
     1104    }
    10701105}
  • src/org/openstreetmap/josm/data/gpx/GpxTrack.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
     4import java.awt.Color;
    45import java.util.Collection;
    56import java.util.Map;
    67
     
    78import org.openstreetmap.josm.data.Bounds;
    89
    910/**
    10  * Read-only gpx track. Implementations doesn't have to be immutable, but should always be thread safe.
     11 * Gpx track. Implementations doesn't have to be immutable, but should always be thread safe.
    1112 * @since 444
    1213 */
    1314public interface GpxTrack extends IWithAttributes {
     
    3738    double length();
    3839
    3940    /**
     41     * Gets the color of this track.
     42     * @return The color, <code>null</code> if not set or not supported by the implementation.
     43     * @since xxx
     44     */
     45    default Color getColor() {
     46        return null;
     47    }
     48
     49    /**
     50     * Sets the color of this track. Not necessarily supported by all implementations.
     51     * @param color
     52     * @since xxx
     53     */
     54    default void setColor(Color color) {
     55        return;
     56    }
     57
     58    /**
    4059     * Add a listener that listens to changes in the GPX track.
    4160     * @param l The listener
    4261     */
  • src/org/openstreetmap/josm/data/gpx/IWithAttributes.java

     
    22package org.openstreetmap.josm.data.gpx;
    33
    44import java.util.Collection;
     5import java.util.Map;
    56
    67/**
    78 * Object with attributes (in the context of GPX data).
     
    5051    void put(String key, Object value);
    5152
    5253    /**
    53      * Add a key / value pair that is not part of the GPX schema as an extension.
    54      *
     54     * Add a key / value pair that is not part of the GPX schema as an extension and the extension attribute if not already present.
     55     * @param extensionType type of the extension, can be any <code>GpxConstants.EXTENSIONS_*</code> or a string of format <code>"extensions.<i>xmlns_prefix</i>"</code>
    5556     * @param key the key
    5657     * @param value the value
     58     * @see GpxConstants#EXTENSIONS_PREFIX
    5759     */
    58     void addExtension(String key, String value);
     60    void addExtensionKey(String extensionType, String key, String value);
    5961
     62    /**
     63     * Add key / value pairs that are not part of the GPX schema as extensions.
     64     * @param extensionMap the <code>Map&lt;String, String&gt;</code> with the key being the qualified name including prefix of the namespace
     65     * @see GpxConstants#EXTENSIONS_PREFIX
     66     * @see Extensions
     67     */
     68    void addExtensions(Map<String, String> extensionMap);
     69
     70    /**
     71     * Removes a key from the extension if present and the extension if empty
     72     * @param extensionType type of the extension, can be any <code>GpxConstants.EXTENSIONS_*</code> or a string of format <code>"extensions.<i>xmlns_prefix</i>"</code>
     73     * @param key the key, not case-sensitive
     74     * @see GpxConstants#EXTENSIONS_PREFIX
     75     */
     76    void removeExtensionKey(String extensionType, String key);
     77
    6078}
  • src/org/openstreetmap/josm/data/gpx/ImmutableGpxTrack.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
     4import java.awt.Color;
    45import java.util.ArrayList;
    56import java.util.Collection;
    67import java.util.Collections;
     
    910import java.util.Map;
    1011
    1112import org.openstreetmap.josm.data.Bounds;
     13import org.openstreetmap.josm.tools.ListenerList;
     14import org.openstreetmap.josm.tools.Logging;
    1215
    1316/**
    1417 * Immutable GPX track.
     18 * Note that the color attributes are not immutable and may be modified by the user.
    1519 * @since 2907
    1620 */
    1721public class ImmutableGpxTrack extends WithAttributes implements GpxTrack {
     
    1923    private final List<GpxTrackSegment> segments;
    2024    private final double length;
    2125    private final Bounds bounds;
     26    private Color colorCache;
     27    private final ListenerList<GpxTrackChangeListener> listeners = ListenerList.create();
    2228
    2329    /**
    24      * Constructs a new {@code ImmutableGpxTrack}.
     30     * Constructs a new {@code ImmutableGpxTrack} and adds the extensions.
    2531     * @param trackSegs track segments
    2632     * @param attributes track attributes
     33     * @param extensionMap map of extensions
    2734     */
    28     public ImmutableGpxTrack(Collection<Collection<WayPoint>> trackSegs, Map<String, Object> attributes) {
     35    public ImmutableGpxTrack(Collection<Collection<WayPoint>> trackSegs, Map<String, Object> attributes, Map<String, String> extensionMap) {
    2936        List<GpxTrackSegment> newSegments = new ArrayList<>();
    3037        for (Collection<WayPoint> trackSeg: trackSegs) {
    3138            if (trackSeg != null && !trackSeg.isEmpty()) {
     
    3239                newSegments.add(new ImmutableGpxTrackSegment(trackSeg));
    3340            }
    3441        }
    35         this.attr = Collections.unmodifiableMap(new HashMap<>(attributes));
     42        this.attr = new HashMap<>(attributes);
     43        if (extensionMap != null) {
     44            addExtensions(extensionMap);
     45        }
    3646        this.segments = Collections.unmodifiableList(newSegments);
    3747        this.length = calculateLength();
    3848        this.bounds = calculateBounds();
     
    3949    }
    4050
    4151    /**
     52     * Constructs a new {@code ImmutableGpxTrack}.
     53     * @param trackSegs track segments
     54     * @param attributes track attributes
     55     */
     56    public ImmutableGpxTrack(Collection<Collection<WayPoint>> trackSegs, Map<String, Object> attributes) {
     57        this(trackSegs, attributes, null);
     58    }
     59
     60    /**
    4261     * Constructs a new {@code ImmutableGpxTrack} from {@code GpxTrackSegment} objects.
    4362     * @param segments The segments to build the track from.  Input is not deep-copied,
    4463     *                 which means the caller may reuse the same segments to build
     
    4867     * @since 13210
    4968     */
    5069    public ImmutableGpxTrack(List<GpxTrackSegment> segments, Map<String, Object> attributes) {
    51         this.attr = Collections.unmodifiableMap(new HashMap<>(attributes));
     70        this.attr = new HashMap<>(attributes);
    5271        this.segments = Collections.unmodifiableList(segments);
    5372        this.length = calculateLength();
    5473        this.bounds = calculateBounds();
     
    7998    }
    8099
    81100    @Override
     101    public void setColor(Color color) {
     102        setColorAttr(color);
     103        colorCache = color;
     104    }
     105
     106    private void setColorAttr(Color color) {
     107        removeExtensionKey(GpxConstants.EXTENSIONS_GARMIN, "displaycolor");
     108        if (color != null) {
     109            addExtensionKey(EXTENSIONS_DRAWING, "color", String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()));
     110        } else {
     111            removeExtensionKey(EXTENSIONS_DRAWING, "color");
     112        }
     113    }
     114
     115    @Override
     116    public Color getColor() {
     117        if (colorCache == null) {
     118            colorCache = getColorFromAttr(attr);
     119        }
     120        return colorCache;
     121    }
     122
     123
     124    private Color getColorFromAttr(Map<String, Object> attr) {
     125        Extensions gpxd = (Extensions) attr.get(GpxConstants.EXTENSIONS_DRAWING);
     126        if (gpxd != null) {
     127            String cs = gpxd.get("color");
     128            try {
     129                return Color.decode(cs);
     130            } catch (NumberFormatException ex) {
     131                Logging.warn("Could not read gpxd color: " + cs);
     132            }
     133        } else {
     134            Extensions gpxx = (Extensions) attr.get(GpxConstants.EXTENSIONS_GARMIN);
     135            if (gpxx != null) {
     136                String cs = gpxx.get("displaycolor");
     137                if (cs != null) {
     138                    Color cc = GpxConstants.GARMIN_COLORS.get(cs);
     139                    if (cc != null) {
     140                        return cc;
     141                    }
     142                }
     143                Logging.warn("Could not read garmin color: " + cs);
     144            }
     145        }
     146        return null;
     147    }
     148
     149    @Override
     150    public void addExtensionKey(String extensionType, String key, String value) {
     151        colorCache = null;
     152        super.addExtensionKey(extensionType, key, value);
     153        listeners.fireEvent(l -> l.gpxDataChanged(new GpxTrackChangeEvent(this)));
     154    }
     155
     156    @Override
     157    public void removeExtensionKey(String extensionType, String key) {
     158        colorCache = null;
     159        super.removeExtensionKey(extensionType, key);
     160        listeners.fireEvent(l -> l.gpxDataChanged(new GpxTrackChangeEvent(this)));
     161    }
     162
     163    @Override
    82164    public Map<String, Object> getAttributes() {
    83165        return attr;
    84166    }
     
    119201            return false;
    120202        return true;
    121203    }
     204
     205    @Override
     206    public void addListener(GpxTrackChangeListener l) {
     207        listeners.addListener(l);
     208    }
     209
     210    @Override
     211    public void removeListener(GpxTrackChangeListener l) {
     212        listeners.removeListener(l);
     213    }
     214
    122215}
  • src/org/openstreetmap/josm/data/gpx/Line.java

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.data.gpx;
    3 
    4 import java.util.Collection;
    5 import java.util.Iterator;
    6 import java.util.Map;
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.gpx;
     3
     4import java.awt.Color;
     5import java.util.Collection;
     6import java.util.Iterator;
     7import java.util.Map;
    78import java.util.Objects;
    89
    910/**
     
    1011 * Line represents a linear collection of GPX waypoints with the ordered/unordered distinction.
    1112 * @since 14451
    1213 */
    13 public class Line implements Collection<WayPoint> {
    14     private final Collection<WayPoint> waypoints;
    15     private final boolean unordered;
     14public class Line implements Collection<WayPoint> {
     15    private final Collection<WayPoint> waypoints;
     16    private final boolean unordered;
     17    private final Color color;
     18
     19    /**
     20     * Constructs a new {@code Line}.
     21     * @param waypoints collection of waypoints
     22     * @param attributes track/route attributes
     23     * @param color color of the track
     24     *
     25     */
     26    public Line(Collection<WayPoint> waypoints, Map<String, Object> attributes, Color color) {
     27        this.color = color;
     28        this.waypoints = Objects.requireNonNull(waypoints);
     29        unordered = attributes.isEmpty() && waypoints.stream().allMatch(x -> x.get(GpxConstants.PT_TIME) == null);
     30    }
    1631
    1732    /**
    1833     * Constructs a new {@code Line}.
    19      * @param waypoints collection of waypoints
    20      * @param attributes track/route attributes
    21      */
    22     public Line(Collection<WayPoint> waypoints, Map<String, Object> attributes) {
    23         this.waypoints = Objects.requireNonNull(waypoints);
    24         unordered = attributes.isEmpty() && waypoints.stream().allMatch(x -> x.get(GpxConstants.PT_TIME) == null);
    25     }
    26 
    27     /**
     34     * @param trackSegment track segment
     35     * @param trackAttributes track attributes
     36     * @param color color of the track
     37     */
     38    public Line(GpxTrackSegment trackSegment, Map<String, Object> trackAttributes, Color color) {
     39        this(trackSegment.getWayPoints(), trackAttributes, color);
     40    }
     41
     42    /**
    2843     * Constructs a new {@code Line}.
    29      * @param trackSegment track segment
    30      * @param trackAttributes track attributes
    31      */
    32     public Line(GpxTrackSegment trackSegment, Map<String, Object> trackAttributes) {
    33         this(trackSegment.getWayPoints(), trackAttributes);
    34     }
    35 
    36     /**
    37      * Constructs a new {@code Line}.
    38      * @param route route
    39      */
    40     public Line(GpxRoute route) {
    41         this(route.routePoints, route.attr);
    42     }
    43 
    44     /**
     44     * @param route route
     45     */
     46    public Line(GpxRoute route) {
     47        this(route.routePoints, route.attr, null);
     48    }
     49
     50    /**
    4551     * Determines if waypoints are ordered.
    4652     * @return {@code true} if waypoints are ordered
    4753     */
    4854    public boolean isUnordered() {
    49         return unordered;
     55        return unordered;
     56    }
     57
     58    /**
     59     * Returns the track/route color
     60     * @return the color
     61     */
     62    public Color getColor() {
     63        return color;
     64    }
     65
     66    @Override
     67    public int size() {
     68        return waypoints.size();
    5069    }
    5170
    5271    @Override
    53     public int size() {
    54         return waypoints.size();
    55     }
    56 
    57     @Override
    5872    public boolean isEmpty() {
    5973        return waypoints.isEmpty();
    6074    }
  • src/org/openstreetmap/josm/data/gpx/WithAttributes.java

     
    44import java.util.Collection;
    55import java.util.HashMap;
    66import java.util.Map;
     7import java.util.Map.Entry;
    78
    89/**
    910 * Default implementation for IWithAttributes.
     
    6465
    6566    /**
    6667     * Put a key / value pair as a new attribute.
    67      *
    6868     * Overrides key / value pair with the same key (if present).
    6969     *
    7070     * @param key the key
     
    7575        attr.put(key, value);
    7676    }
    7777
    78     /**
    79      * Add a key / value pair that is not part of the GPX schema as an extension.
    80      *
    81      * @param key the key
    82      * @param value the value
    83      */
    8478    @Override
    85     public void addExtension(String key, String value) {
    86         if (!attr.containsKey(META_EXTENSIONS)) {
    87             attr.put(META_EXTENSIONS, new Extensions());
     79    public void addExtensionKey(String extensionType, String key, String value) {
     80        Extensions ext;
     81        if (!attr.containsKey(extensionType)) {
     82            ext = new Extensions(extensionType);
     83            attr.put(extensionType, ext);
     84        } else {
     85            ext = (Extensions) attr.get(extensionType);
    8886        }
    89         Extensions ext = (Extensions) attr.get(META_EXTENSIONS);
    9087        ext.put(key, value);
    9188    }
    9289
    9390    @Override
     91    public void addExtensions(Map<String, String> extensionMap) {
     92        for (Entry<String, String> e : extensionMap.entrySet()) {
     93            String k = e.getKey();
     94            int dot = k.indexOf(":");
     95            if (dot != -1) {
     96                addExtensionKey(GpxConstants.EXTENSIONS_PREFIX + k.substring(0, dot), k.substring(dot + 1), e.getValue());
     97            }
     98        }
     99    }
     100
     101    @Override
     102    public void removeExtensionKey(String extensionType, String key) {
     103        Extensions ext = (Extensions) attr.get(extensionType);
     104        if (ext != null) {
     105            ext.remove(key);
     106            if (ext.isEmpty()) {
     107                attr.remove(extensionType);
     108            }
     109        }
     110    }
     111
     112    @Override
    94113    public int hashCode() {
    95114        return 31 + ((attr == null) ? 0 : attr.hashCode());
    96115    }
     
    111130            return false;
    112131        return true;
    113132    }
     133
    114134}
  • src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java

     
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.awt.Color;
    76import java.awt.Component;
    87import java.awt.Dimension;
    98import java.awt.Font;
     
    1716import java.util.ArrayList;
    1817import java.util.Arrays;
    1918import java.util.List;
    20 import java.util.Objects;
     19import java.util.Optional;
    2120import java.util.concurrent.CopyOnWriteArrayList;
    2221
    2322import javax.swing.AbstractAction;
     
    4140import org.openstreetmap.josm.actions.MergeLayerAction;
    4241import org.openstreetmap.josm.data.coor.EastNorth;
    4342import org.openstreetmap.josm.data.imagery.OffsetBookmark;
    44 import org.openstreetmap.josm.data.preferences.AbstractProperty;
    4543import org.openstreetmap.josm.gui.MainApplication;
    4644import org.openstreetmap.josm.gui.MapFrame;
    4745import org.openstreetmap.josm.gui.MapView;
     
    583581                label.setFont(label.getFont().deriveFont(Font.BOLD));
    584582            }
    585583            if (Config.getPref().getBoolean("dialog.layer.colorname", true)) {
    586                 AbstractProperty<Color> prop = layer.getColorProperty();
    587                 Color c = prop == null ? null : prop.get();
    588                 if (c == null || model.getLayers().stream()
    589                         .map(Layer::getColorProperty)
    590                         .filter(Objects::nonNull)
    591                         .map(AbstractProperty::get)
    592                         .noneMatch(oc -> oc != null && !oc.equals(c))) {
    593                     /* not more than one color, don't use coloring */
    594                     label.setForeground(UIManager.getColor(isSelected ? "Table.selectionForeground" : "Table.foreground"));
    595                 } else {
    596                     label.setForeground(c);
    597                 }
     584                label.setForeground(Optional
     585                        .ofNullable(layer.getColor())
     586                        .orElse(UIManager.getColor(isSelected ? "Table.selectionForeground" : "Table.foreground")));
    598587            }
    599588            label.setIcon(layer.getIcon());
    600589            label.setToolTipText(layer.getToolTipText());
  • src/org/openstreetmap/josm/gui/dialogs/layer/LayerVisibilityAction.java

     
    593593                    List<Layer> layers = layerSupplier.get();
    594594                    for (Layer l : layers) {
    595595                        if (l instanceof GpxLayer) {
    596                             l.getColorProperty().put(color);
     596                            l.setColor(color);
    597597                        }
    598598                    }
    599599                    highlightColor(color);
     
    602602            add(colorPanel, GBC.std().weight(1, 1).fill().insets(5));
    603603            panels.put(color, colorPanel);
    604604
    605             List<Color> colors = layerSupplier.get().stream().map(l -> l.getColorProperty().get()).distinct().collect(Collectors.toList());
     605            List<Color> colors = layerSupplier.get().stream().map(l -> l.getColor()).distinct().collect(Collectors.toList());
    606606            if (colors.size() == 1) {
    607607                highlightColor(colors.get(0));
    608608            }
     
    611611        @Override
    612612        public void updateLayers(List<Layer> layers, boolean allVisible, boolean allHidden) {
    613613            List<Color> colors = layers.stream().filter(l -> l instanceof GpxLayer)
    614                     .map(l -> ((GpxLayer) l).getColorProperty().get())
     614                    .map(l -> ((GpxLayer) l).getColor())
    615615                    .distinct()
    616616                    .collect(Collectors.toList());
    617617            if (colors.size() == 1) {
  • src/org/openstreetmap/josm/gui/io/importexport/GpxExporter.java

     
    2525
    2626import org.openstreetmap.josm.data.gpx.GpxConstants;
    2727import org.openstreetmap.josm.data.gpx.GpxData;
     28import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    2829import org.openstreetmap.josm.gui.ExtendedDialog;
    2930import org.openstreetmap.josm.gui.MainApplication;
    3031import org.openstreetmap.josm.gui.layer.GpxLayer;
     
    146147        p.add(new JLabel(tr("Keywords")), GBC.eol());
    147148        JosmTextField keywords = new JosmTextField();
    148149        keywords.setText(gpxData.getString(META_KEYWORDS));
    149         p.add(keywords, GBC.eop().fill(GBC.HORIZONTAL));
     150        p.add(keywords, GBC.eol().fill(GBC.HORIZONTAL));
    150151
     152        boolean sel = Config.getPref().getBoolean("gpx.export.colors", true);
     153        JCheckBox colors = new JCheckBox(tr("Save track colors in GPX file"), sel);
     154        p.add(colors, GBC.eol().fill(GBC.HORIZONTAL));
     155        JCheckBox garmin = new JCheckBox(tr("Use Garmin compatible GPX extensions"),
     156                Config.getPref().getBoolean("gpx.export.colors.garmin", false));
     157        garmin.setEnabled(sel);
     158        p.add(garmin, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 0, 0, 0));
     159
     160        boolean hasPrefs = !gpxData.layerPrefs.isEmpty();
     161        JCheckBox layerPrefs = new JCheckBox(tr("Save layer specific preferences"),
     162                hasPrefs && Config.getPref().getBoolean("gpx.export.prefs", true));
     163        layerPrefs.setEnabled(hasPrefs);
     164        p.add(layerPrefs, GBC.eop().fill(GBC.HORIZONTAL));
     165
    151166        ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(),
    152167                tr("Export options"),
    153168                tr("Export and Save"), tr("Cancel"))
     
    154169            .setButtonIcons("exportgpx", "cancel")
    155170            .setContent(p);
    156171
     172        colors.addActionListener(l -> {
     173            garmin.setEnabled(colors.isSelected());
     174        });
     175
     176        garmin.addActionListener(l -> {
     177            if (garmin.isSelected() &&
     178                    !ConditionalOptionPaneUtil.showConfirmationDialog(
     179                            "gpx_color_garmin",
     180                            ed,
     181                            new JLabel(tr("<html>Garmin track extensions only support 16 colors. If you continue, the closest supported<br>track color will be used and information such as width and opacity will be lost.</html>")),
     182                            tr("Information"),
     183                            JOptionPane.OK_CANCEL_OPTION,
     184                            JOptionPane.INFORMATION_MESSAGE,
     185                            JOptionPane.OK_OPTION)) {
     186                garmin.setSelected(false);
     187            }
     188        });
     189
    157190        if (ed.showDialog().getValue() != 1) {
    158191            setCanceled(true);
    159192            return;
     
    167200        if (!copyright.getText().isEmpty()) {
    168201            Config.getPref().put("lastCopyright", copyright.getText());
    169202        }
     203        Config.getPref().putBoolean("gpx.export.colors", colors.isSelected());
     204        Config.getPref().putBoolean("gpx.export.colors.garmin", garmin.isSelected());
     205        if (hasPrefs) {
     206            Config.getPref().putBoolean("gpx.export.prefs", layerPrefs.isSelected());
     207        }
     208        String colorFormat = null;
     209        if (colors.isSelected()) {
     210            colorFormat = garmin.isSelected()
     211                    ? GpxConstants.EXTENSIONS_GARMIN
     212                    : GpxConstants.EXTENSIONS_DRAWING;
     213        }
    170214
    171215        if (layer instanceof OsmDataLayer) {
    172216            gpxData = ((OsmDataLayer) layer).toGpxData();
     
    203247            gpxData.put(META_KEYWORDS, keywords.getText());
    204248        }
    205249
    206         try (OutputStream fo = Compression.getCompressedFileOutputStream(file); GpxWriter writer = new GpxWriter(fo)) {
    207             writer.write(gpxData);
     250        try (OutputStream fo = Compression.getCompressedFileOutputStream(file)) {
     251            GpxWriter w = new GpxWriter(fo);
     252            w.write(gpxData, colorFormat, layerPrefs.isSelected());
     253            w.close();
     254            fo.flush();
     255            gpxData.setModified(false);
    208256        }
    209257    }
    210258
  • src/org/openstreetmap/josm/gui/io/importexport/GpxImporter.java

     
    149149            markerLayer = new MarkerLayer(data, markerLayerName, data.storageFile, gpxLayer);
    150150            if (markerLayer.data.isEmpty()) {
    151151                markerLayer = null;
     152            } else {
     153                gpxLayer.linkedMarkerLayer = markerLayer;
    152154            }
    153155        }
    154156        Runnable postLayerTask = () -> {
  • src/org/openstreetmap/josm/gui/layer/CustomizeColor.java

     
    1818import javax.swing.JMenuItem;
    1919import javax.swing.JOptionPane;
    2020
    21 import org.openstreetmap.josm.data.preferences.AbstractProperty;
    2221import org.openstreetmap.josm.gui.MainApplication;
    2322import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
    2423import org.openstreetmap.josm.gui.layer.Layer.LayerAction;
    2524import org.openstreetmap.josm.gui.layer.Layer.MultiLayerAction;
    26 import org.openstreetmap.josm.tools.CheckParameterUtil;
    2725import org.openstreetmap.josm.tools.ImageProvider;
    2826
    2927/**
     
    3331 * of a certain {@link GpxLayer} or {@link org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer}.
    3432 */
    3533public class CustomizeColor extends AbstractAction implements LayerAction, MultiLayerAction {
    36     private final transient List<AbstractProperty<Color>> colors;
     34    private final transient List<Layer> colorLayers;
    3735
    3836    /**
    3937     * Constructs a new {@code CustomizeColor} for a given list of layers.
     
    4240    public CustomizeColor(List<Layer> l) {
    4341        super(tr("Customize Color"));
    4442        new ImageProvider("colorchooser").getResource().attachImageIcon(this, true);
    45         colors = l.stream().map(Layer::getColorProperty).collect(Collectors.toList());
    46         CheckParameterUtil.ensureThat(colors.stream().allMatch(Objects::nonNull), "All layers must have colors.");
     43        colorLayers = l.stream().filter(Objects::nonNull).filter(Layer::hasColor).collect(Collectors.toList());
    4744        putValue("help", ht("/Action/LayerCustomizeColor"));
    4845    }
    4946
     
    5754
    5855    @Override
    5956    public boolean supportLayers(List<Layer> layers) {
    60         return layers.stream().allMatch(l -> l.getColorProperty() != null);
     57        return layers.stream().allMatch(Layer::hasColor);
    6158    }
    6259
    6360    @Override
     
    7269
    7370    @Override
    7471    public void actionPerformed(ActionEvent e) {
    75         Color cl = colors.stream().map(AbstractProperty::get).filter(Objects::nonNull).findAny().orElse(Color.GRAY);
     72        Color cl = colorLayers.stream().filter(Objects::nonNull).map(Layer::getColor).filter(Objects::nonNull).findAny().orElse(Color.GRAY);
    7673        JColorChooser c = new JColorChooser(cl);
    7774        Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
    7875        int answer = JOptionPane.showOptionDialog(
     
    8784        );
    8885        switch (answer) {
    8986        case 0:
    90             colors.stream().forEach(prop -> prop.put(c.getColor()));
     87            colorLayers.stream().forEach(l -> l.setColor(c.getColor()));
    9188            break;
    9289        case 1:
    9390            return;
    9491        case 2:
    95             colors.stream().forEach(prop -> prop.put(null));
     92            colorLayers.stream().forEach(l -> l.setColor(null));
    9693            break;
    9794        }
    9895        // TODO: Make the layer dialog listen to property change events so that this is not needed any more.
  • src/org/openstreetmap/josm/gui/layer/GpxLayer.java

     
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55import static org.openstreetmap.josm.tools.I18n.trn;
    66
     7import java.awt.Color;
    78import java.awt.Dimension;
    89import java.awt.Graphics2D;
    910import java.awt.event.ActionEvent;
     
    3132import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener;
    3233import org.openstreetmap.josm.data.gpx.GpxTrack;
    3334import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    34 import org.openstreetmap.josm.data.preferences.NamedColorProperty;
    3535import org.openstreetmap.josm.data.projection.Projection;
    3636import org.openstreetmap.josm.gui.MapView;
    3737import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
     
    4646import org.openstreetmap.josm.gui.layer.gpx.ImportAudioAction;
    4747import org.openstreetmap.josm.gui.layer.gpx.ImportImagesAction;
    4848import org.openstreetmap.josm.gui.layer.gpx.MarkersFromNamedPointsAction;
     49import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
     50import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
    4951import org.openstreetmap.josm.gui.widgets.HtmlPanel;
    5052import org.openstreetmap.josm.tools.ImageProvider;
    5153import org.openstreetmap.josm.tools.Utils;
     
    5456/**
    5557 * A layer that displays data from a Gpx file / the OSM gpx downloads.
    5658 */
    57 public class GpxLayer extends Layer implements ExpertModeChangeListener {
     59public class GpxLayer extends AbstractModifiableLayer implements ExpertModeChangeListener {
    5860
    5961    /** GPX data */
    6062    public GpxData data;
    6163    private final boolean isLocalFile;
    6264    private boolean isExpertMode;
     65
    6366    /**
     67     * The MarkerLayer imported from the same file.
     68     */
     69    public MarkerLayer linkedMarkerLayer;
     70
     71    /**
    6472     * used by {@link ChooseTrackVisibilityAction} to determine which tracks to show/hide
    6573     *
    6674     * Call {@link #invalidate()} after each change!
     
    108116    }
    109117
    110118    @Override
    111     protected NamedColorProperty getBaseColorProperty() {
    112         return GpxDrawHelper.DEFAULT_COLOR;
     119    public Color getColor() {
     120        Color[] c = data.getTracks().stream().map(t -> t.getColor()).distinct().toArray(Color[]::new);
     121        return c.length == 1 ? c[0] : null; //only return if exactly one distinct color present
    113122    }
    114123
     124    @Override
     125    public void setColor(Color color) {
     126        for (GpxTrack trk : data.getTracks()) {
     127            trk.setColor(color);
     128        }
     129        GPXSettingsPanel.putLayerPrefLocal(this, "colormode", "0");
     130    }
     131
     132    @Override
     133    public boolean hasColor() {
     134        return true;
     135    }
     136
    115137    /**
    116138     * Returns a human readable string that shows the timespan of the given track
    117139     * @param trk The GPX track for which timespan is displayed
     
    480502    }
    481503
    482504    @Override
     505    public boolean isModified() {
     506        return data.isModified();
     507    }
     508
     509    @Override
     510    public boolean requiresSaveToFile() {
     511        return isModified() && isLocalFile();
     512    }
     513
     514    @Override
    483515    public String getChangesetSourceTag() {
    484516        // no i18n for international values
    485517        return "survey";
  • src/org/openstreetmap/josm/gui/layer/Layer.java

     
    2525import org.openstreetmap.josm.actions.SaveAsAction;
    2626import org.openstreetmap.josm.data.ProjectionBounds;
    2727import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    28 import org.openstreetmap.josm.data.preferences.AbstractProperty;
    29 import org.openstreetmap.josm.data.preferences.AbstractProperty.ValueChangeListener;
    30 import org.openstreetmap.josm.data.preferences.NamedColorProperty;
    3128import org.openstreetmap.josm.data.projection.Projection;
    3229import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
    3330import org.openstreetmap.josm.gui.MainApplication;
     
    164161     */
    165162    private File associatedFile;
    166163
    167     private final ValueChangeListener<Object> invalidateListener = change -> invalidate();
    168164    private boolean isDestroyed;
    169165
    170166    /**
     
    198194    public abstract Icon getIcon();
    199195
    200196    /**
    201      * Gets the color property to use for this layer.
    202      * @return The color property.
    203      * @since 10824
     197     * @return whether the layer has / can handle colors.
    204198     */
    205     public AbstractProperty<Color> getColorProperty() {
    206         NamedColorProperty base = getBaseColorProperty();
    207         if (base != null) {
    208             return base.getChildColor(NamedColorProperty.COLOR_CATEGORY_LAYER, getName(), base.getName());
    209         } else {
    210             return null;
    211         }
     199    public boolean hasColor() {
     200        return false;
    212201    }
    213202
    214203    /**
    215      * Gets the color property that stores the default color for this layer.
    216      * @return The property or <code>null</code> if this layer is not colored.
    217      * @since 10824
     204     * Return the current color of the layer
     205     * @return null when not present or not supported
    218206     */
    219     protected NamedColorProperty getBaseColorProperty() {
     207    public Color getColor() {
    220208        return null;
    221209    }
    222210
    223     private void addColorPropertyListener() {
    224         AbstractProperty<Color> colorProperty = getColorProperty();
    225         if (colorProperty != null) {
    226             colorProperty.addListener(invalidateListener);
    227         }
     211    /**
     212     * Sets the color for this layer. Nothing happens if not supported by the layer
     213     * @param color the color to be set, <code>null</code> for default
     214     */
     215    public void setColor(Color color) {
    228216    }
    229217
    230     private void removeColorPropertyListener() {
    231         AbstractProperty<Color> colorProperty = getColorProperty();
    232         if (colorProperty != null) {
    233             colorProperty.removeListener(invalidateListener);
    234         }
    235     }
    236 
    237218    /**
    238219     * @return A small tooltip hint about some statistics for this layer.
    239220     */
     
    302283        }
    303284        isDestroyed = true;
    304285        // Override in subclasses if needed
    305         removeColorPropertyListener();
    306286    }
    307287
    308288    /**
     
    339319     * @param name the name. If null, the name is set to the empty string.
    340320     */
    341321    public void setName(String name) {
    342         if (this.name != null) {
    343             removeColorPropertyListener();
    344         }
    345322        String oldValue = this.name;
    346323        this.name = Optional.ofNullable(name).orElse("");
    347324        if (!this.name.equals(oldValue)) {
    348325            propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
    349326        }
    350 
    351         // re-add listener
    352         addColorPropertyListener();
    353327        invalidate();
    354328    }
    355329
  • src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

     
    3131import java.util.LinkedHashMap;
    3232import java.util.List;
    3333import java.util.Map;
     34import java.util.Map.Entry;
    3435import java.util.Optional;
    3536import java.util.Set;
    3637import java.util.concurrent.CopyOnWriteArrayList;
     
    780781            Collection<Collection<WayPoint>> trk = new ArrayList<>();
    781782            Map<String, Object> trkAttr = new HashMap<>();
    782783
    783             String name = gpxVal(w, "name");
    784             if (name != null) {
    785                 trkAttr.put("name", name);
     784            Map<String, String> trkExts = new HashMap<>();
     785            for (Entry<String, String> e : w.getKeys().entrySet()) {
     786                //String k = e.getKey().replaceFirst("/^" + GpxConstants.GPX_PREFIX + "/", "");
     787                String k = e.getKey().startsWith(GpxConstants.GPX_PREFIX) ? e.getKey().substring(GpxConstants.GPX_PREFIX.length()) : e.getKey();
     788                String v = e.getValue();
     789                if (GpxConstants.RTE_TRK_KEYS.contains(k)) {
     790                    trkAttr.put(k, v);
     791                } else if (GpxConstants.SUPPORTED_EXTENSION_NAPMESPACES.stream().anyMatch(
     792                        s -> k.startsWith(s + ":"))) {
     793                    trkExts.put(k, v);
     794                }
    786795            }
    787 
    788796            List<WayPoint> trkseg = null;
    789797            for (Node n : w.getNodes()) {
    790798                if (!n.isUsable()) {
     
    801809                trkseg.add(nodeToWayPoint(n));
    802810            }
    803811
    804             gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr));
     812            gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr, trkExts));
    805813        });
    806814    }
    807815
  • src/org/openstreetmap/josm/gui/layer/gpx/ChooseTrackVisibilityAction.java

     
    44import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
    55import static org.openstreetmap.josm.tools.I18n.tr;
    66
     7import java.awt.Color;
    78import java.awt.Component;
    89import java.awt.Dimension;
    910import java.awt.GridBagLayout;
     
    1213import java.awt.event.MouseEvent;
    1314import java.awt.event.MouseListener;
    1415import java.io.Serializable;
     16import java.util.ArrayList;
    1517import java.util.Arrays;
    1618import java.util.Comparator;
     19import java.util.List;
    1720import java.util.Map;
     21import java.util.Objects;
    1822import java.util.Optional;
    1923
    2024import javax.swing.AbstractAction;
     25import javax.swing.JColorChooser;
    2126import javax.swing.JComponent;
    2227import javax.swing.JLabel;
     28import javax.swing.JOptionPane;
    2329import javax.swing.JPanel;
    2430import javax.swing.JScrollPane;
    2531import javax.swing.JTable;
    2632import javax.swing.JToggleButton;
    2733import javax.swing.ListSelectionModel;
     34import javax.swing.event.TableModelEvent;
    2835import javax.swing.table.DefaultTableModel;
    2936import javax.swing.table.TableCellRenderer;
    3037import javax.swing.table.TableRowSorter;
    3138
     39import org.apache.commons.jcs.access.exception.InvalidArgumentException;
    3240import org.openstreetmap.josm.data.SystemOfMeasurement;
    3341import org.openstreetmap.josm.data.gpx.GpxConstants;
    3442import org.openstreetmap.josm.data.gpx.GpxTrack;
     
    5563     * @param layer The associated GPX layer
    5664     */
    5765    public ChooseTrackVisibilityAction(final GpxLayer layer) {
    58         super(tr("Choose visible tracks"));
     66        super(tr("Choose track visibility and colors"));
    5967        new ImageProvider("dialogs/filter").getResource().attachImageIcon(this, true);
    6068        this.layer = layer;
    6169        putValue("help", ht("/Action/ChooseTrackVisibility"));
     
    116124            String time = GpxLayer.getTimespanForTrack(trk);
    117125            TrackLength length = new TrackLength(trk.length());
    118126            String url = (String) Optional.ofNullable(attr.get("url")).orElse("");
    119             tracks[i] = new Object[]{name, desc, time, length, url};
     127            tracks[i] = new Object[]{name, desc, time, length, url, trk};
    120128            i++;
    121129        }
    122130        return tracks;
    123131    }
    124132
     133    private void showColorDialog(List<GpxTrack> tracks) {
     134        Color cl = tracks.stream().filter(Objects::nonNull)
     135                .map(GpxTrack::getColor).filter(Objects::nonNull)
     136                .findAny().orElse(GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get());
     137        JColorChooser c = new JColorChooser(cl);
     138        Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
     139        int answer = JOptionPane.showOptionDialog(
     140                MainApplication.getMainFrame(),
     141                c,
     142                tr("Choose a color"),
     143                JOptionPane.OK_CANCEL_OPTION,
     144                JOptionPane.PLAIN_MESSAGE,
     145                null,
     146                options,
     147                options[0]
     148        );
     149        switch (answer) {
     150        case 0:
     151            tracks.stream().forEach(t -> t.setColor(c.getColor()));
     152            layer.data.layerPrefs.put("colors", "0"); //set Colormode to none
     153            break;
     154        case 1:
     155            return;
     156        case 2:
     157            tracks.stream().forEach(t -> t.setColor(null));
     158            break;
     159        }
     160        table.repaint();
     161    }
     162
    125163    /**
    126164     * Builds an non-editable table whose 5th column will open a browser when double clicked.
    127165     * The table will fill its parent.
     
    138176                if (c instanceof JComponent) {
    139177                    JComponent jc = (JComponent) c;
    140178                    jc.setToolTipText(getValueAt(row, col).toString());
     179                    if (content.length > row
     180                            && content[row].length > 5
     181                            && content[row][5] instanceof GpxTrack) {
     182                        Color color = ((GpxTrack) content[row][5]).getColor();
     183                        if (color != null) {
     184                            double brightness = Math.sqrt(Math.pow(color.getRed(), 2) * .241
     185                                    + Math.pow(color.getGreen(), 2) * .691
     186                                    + Math.pow(color.getBlue(), 2) * .068);
     187                            if (brightness > 250) {
     188                                color = color.darker();
     189                            }
     190                            if (isRowSelected(row)) {
     191                                jc.setBackground(color);
     192                                if (brightness <= 130) {
     193                                    jc.setForeground(Color.WHITE);
     194                                } else {
     195                                    jc.setForeground(Color.BLACK);
     196                                }
     197                            } else {
     198                                if (brightness > 200) {
     199                                    color = color.darker(); //brightness >250 is darkened twice on purpose
     200                                }
     201                                jc.setForeground(color);
     202                                jc.setBackground(Color.WHITE);
     203                            }
     204                        }
     205                    }
    141206                }
    142207                return c;
    143208            }
     
    144209
    145210            @Override
    146211            public boolean isCellEditable(int rowIndex, int colIndex) {
    147                 return false;
     212                return colIndex <= 1;
    148213            }
     214
     215            @Override
     216            public void tableChanged(TableModelEvent e) {
     217                super.tableChanged(e);
     218                int col = e.getColumn();
     219                int row = e.getFirstRow();
     220                if (row >= 0 && row < content.length && col >= 0 && col <= 1) {
     221                    Object t = content[row][5];
     222                    String val = (String) getValueAt(row, col);
     223                    if (t != null && t instanceof GpxTrack) {
     224                        GpxTrack trk = (GpxTrack) t;
     225                        if (col == 0) {
     226                            trk.put("name", val);
     227                        } else {
     228                            trk.put("desc", val);
     229                        }
     230                    } else {
     231                        throw new InvalidArgumentException();
     232                    }
     233                }
     234            }
    149235        };
    150236        // define how to sort row
    151237        TableRowSorter<DefaultTableModel> rowSorter = new TableRowSorter<>();
     
    249335
    250336        msg.add(new JLabel(tr("<html>Select all tracks that you want to be displayed. " +
    251337                "You can drag select a range of tracks or use CTRL+Click to select specific ones. " +
    252                 "The map is updated live in the background. Open the URLs by double clicking them.</html>")),
     338                "The map is updated live in the background. Open the URLs by double clicking them, edit name and description by double clicking the cell.</html>")),
    253339                GBC.eop().fill(GBC.HORIZONTAL));
    254340        // build table
    255341        final boolean[] trackVisibilityBackup = layer.trackVisibility.clone();
    256         table = buildTable(buildTableContents());
     342        Object[][] content = buildTableContents();
     343        table = buildTable(content);
    257344        selectVisibleTracksInTable();
    258345        listenToSelectionChanges();
    259346        // make the table scrollable
     
    262349
    263350        int v = 1;
    264351        // build dialog
    265         ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), tr("Set track visibility for {0}", layer.getName()),
    266                 tr("Show all"), tr("Show selected only"), tr("Cancel"));
    267         ed.setButtonIcons("eye", "dialogs/filter", "cancel");
     352        ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(),
     353                tr("Set track visibility for {0}", layer.getName()),
     354                tr("Set color for selected tracks..."), tr("Show all"), tr("Show selected only"), tr("Cancel")) {
     355            @Override
     356            protected void buttonAction(int buttonIndex, ActionEvent evt) {
     357                if (buttonIndex == 0) {
     358                    List<GpxTrack> trks = new ArrayList<>();
     359                    for (int i : table.getSelectedRows()) {
     360                        Object trk = content[i][5];
     361                        if (trk != null && trk instanceof GpxTrack) {
     362                            trks.add((GpxTrack) trk);
     363                        }
     364                    }
     365                    showColorDialog(trks);
     366                } else {
     367                    super.buttonAction(buttonIndex, evt);
     368                }
     369            }
     370        };
     371        ed.setButtonIcons("colorchooser", "eye", "dialogs/filter", "cancel");
    268372        ed.setContent(msg, false);
    269373        ed.setDefaultButton(2);
    270374        ed.setCancelButton(3);
     
    275379        dateFilter.saveInPrefs();
    276380        v = ed.getValue();
    277381        // cancel for unknown buttons and copy back original settings
    278         if (v != 1 && v != 2) {
     382        if (v != 2 && v != 3) {
    279383            layer.trackVisibility = Arrays.copyOf(trackVisibilityBackup, layer.trackVisibility.length);
    280384            MainApplication.getMap().repaint();
    281385            return;
    282386        }
    283         // set visibility (1 = show all, 2 = filter). If no tracks are selected
     387        // set visibility (2 = show all, 3 = filter). If no tracks are selected
    284388        // set all of them visible and...
    285389        ListSelectionModel s = table.getSelectionModel();
    286         final boolean all = v == 1 || s.isSelectionEmpty();
     390        final boolean all = v == 2 || s.isSelectionEmpty();
    287391        for (int i = 0; i < layer.trackVisibility.length; i++) {
    288392            layer.trackVisibility[table.convertRowIndexToModel(i)] = all || s.isSelectedIndex(i);
    289393        }
     
    290394        // layer has been changed
    291395        layer.invalidate();
    292396        // ...sync with layer visibility instead to avoid having two ways to hide everything
    293         layer.setVisible(v == 1 || !s.isSelectionEmpty());
     397        layer.setVisible(v == 2 || !s.isSelectionEmpty());
    294398    }
    295399}
  • src/org/openstreetmap/josm/gui/layer/gpx/ConvertFromGpxLayerAction.java

     
    1010import java.util.Collection;
    1111import java.util.Date;
    1212import java.util.List;
     13import java.util.Map;
    1314import java.util.Map.Entry;
    1415
    1516import javax.swing.BorderFactory;
     
    1920import javax.swing.JPanel;
    2021import javax.swing.JRadioButton;
    2122
     23import org.openstreetmap.josm.data.gpx.Extensions;
    2224import org.openstreetmap.josm.data.gpx.GpxConstants;
    2325import org.openstreetmap.josm.data.gpx.GpxTrack;
    2426import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
     
    2527import org.openstreetmap.josm.data.gpx.WayPoint;
    2628import org.openstreetmap.josm.data.osm.DataSet;
    2729import org.openstreetmap.josm.data.osm.Node;
     30import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2831import org.openstreetmap.josm.data.osm.Way;
    2932import org.openstreetmap.josm.gui.ExtendedDialog;
    3033import org.openstreetmap.josm.gui.MainApplication;
     
    6467                List<Node> nodes = new ArrayList<>();
    6568                for (WayPoint p : segment.getWayPoints()) {
    6669                    Node n = new Node(p.getCoor());
    67                     for (Entry<String, Object> entry : p.attr.entrySet()) {
    68                         String key = entry.getKey();
    69                         Object obj = p.get(key);
    70                         if (check && !keys.contains(key) && (obj instanceof String || obj instanceof Number || obj instanceof Date)) {
    71                             keys.add(key);
    72                         }
    73                         if (!none && (obj instanceof String || obj instanceof Number)) {
    74                             // only convert when required
    75                             n.put(GpxConstants.GPX_PREFIX + key, obj.toString());
    76                         } else if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) {
    77                             // timestamps should always be converted
    78                             Date date = (Date) obj;
    79                             if (!none) { //... but the tag will only be set when required
    80                                 n.put(GpxConstants.GPX_PREFIX + key, DateUtils.fromDate(date));
    81                             }
    82                             n.setTimestamp(date);
    83                         }
    84                     }
     70                    addAttributes(p.attr, n, keys, check, none);
    8571                    ds.addPrimitive(n);
    8672                    nodes.add(n);
    8773                }
    8874                Way w = new Way();
    8975                w.setNodes(nodes);
     76                addAttributes(trk.getAttributes(), w, keys, check, none);
    9077                ds.addPrimitive(w);
    9178            }
    9279        }
     
    122109        return ds;
    123110    }
    124111
     112    private static void addAttributes(Map<String, Object> attr, OsmPrimitive n, List<String> keys, boolean check, boolean none) {
     113        for (Entry<String, Object> entry : attr.entrySet()) {
     114            String key = entry.getKey();
     115            Object obj = entry.getValue();
     116            if (check && !keys.contains(key) && (obj instanceof String || obj instanceof Number || obj instanceof Date)) {
     117                keys.add(key);
     118            }
     119            if (!none && (obj instanceof String || obj instanceof Number)) {
     120                // only convert when required
     121                n.put(GpxConstants.GPX_PREFIX + key, obj.toString());
     122            } else if (!none && obj instanceof Extensions) {
     123                Extensions ext = (Extensions) obj;
     124                String pre = ext.getPrefix();
     125                for (Entry<String, String> e : ext.entrySet()) {
     126                    String k = pre + e.getKey();
     127                    if (check && !keys.contains(k)) {
     128                        keys.add(k);
     129                    }
     130                    n.put(k, e.getValue());
     131                }
     132            } else if (obj instanceof Date && GpxConstants.PT_TIME.equals(key)) {
     133                // timestamps should always be converted
     134                Date date = (Date) obj;
     135                if (!none) { //... but the tag will only be set when required
     136                    n.put(GpxConstants.GPX_PREFIX + key, DateUtils.fromDate(date));
     137                }
     138                n.setTimestamp(date);
     139            }
     140        }
     141    }
     142
    125143    /**
    126144     * Filters the tags of the given {@link DataSet}
    127145     * @param ds The {@link DataSet}
     
    131149     */
    132150    public DataSet filterDataSet(DataSet ds, List<String> listPos) {
    133151        Collection<Node> nodes = ds.getNodes();
    134         for (Node n : nodes) {
    135             for (String key : n.keySet()) {
     152        for (OsmPrimitive p : ds.getPrimitives(p -> p instanceof Node || p instanceof Way)) {
     153            for (String key : p.keySet()) {
    136154                if (listPos == null || !listPos.contains(key.substring(GpxConstants.GPX_PREFIX.length()))) {
    137                     n.put(key, null);
     155                   p.put(key, null);
    138156                }
    139157            }
    140158        }
  • src/org/openstreetmap/josm/gui/layer/gpx/CustomizeDrawingAction.java

     
    99import java.awt.event.ActionEvent;
    1010import java.util.LinkedList;
    1111import java.util.List;
     12import java.util.stream.Collectors;
    1213
    1314import javax.swing.AbstractAction;
    1415import javax.swing.Action;
     
    5960
    6061    @Override
    6162    public boolean supportLayers(List<Layer> layers) {
    62         for (Layer layer : layers) {
    63             if (!(layer instanceof GpxLayer)) {
    64                 return false;
    65             }
    66         }
    67         return true;
     63        return layers.stream().allMatch(l -> l instanceof GpxLayer);
    6864    }
    6965
    7066    @Override
     
    7975
    8076    @Override
    8177    public void actionPerformed(ActionEvent e) {
    82         boolean hasLocal = false;
    83         boolean hasNonlocal = false;
    84         for (Layer layer : layers) {
    85             if (layer instanceof GpxLayer) {
    86                 if (((GpxLayer) layer).isLocalFile()) {
    87                     hasLocal = true;
    88                 } else {
    89                     hasNonlocal = true;
    90                 }
    91             }
    92         }
    93         GPXSettingsPanel panel = new GPXSettingsPanel(layers.get(0).getName(), hasLocal, hasNonlocal);
     78        GPXSettingsPanel panel = new GPXSettingsPanel(layers.stream().filter(l -> l instanceof GpxLayer).map(l -> (GpxLayer) l).collect(Collectors.toList()));
    9479        JScrollPane scrollpane = GuiHelper.embedInVerticalScrollPane(panel);
    9580        scrollpane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
    9681        int screenHeight = GuiHelper.getScreenSize().height;
     
    10388        if (answer == JOptionPane.CANCEL_OPTION || answer == JOptionPane.CLOSED_OPTION) {
    10489            return;
    10590        }
    106         for (Layer layer : layers) {
    107             // save preferences for all layers
    108             boolean f = false;
    109             if (layer instanceof GpxLayer) {
    110                 f = ((GpxLayer) layer).isLocalFile();
    111             }
    112             panel.savePreferences(layer.getName(), f);
    113         }
    114         MainApplication.getMap().repaint();
     91        panel.savePreferences();
     92        MainApplication.getMainPanel().repaint();
     93        layers.stream().forEach(l -> l.invalidate());
    11594    }
    11695
    11796}
  • src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java

     
    3232import javax.swing.ImageIcon;
    3333
    3434import org.openstreetmap.josm.data.Bounds;
    35 import org.openstreetmap.josm.data.PreferencesUtils;
    3635import org.openstreetmap.josm.data.SystemOfMeasurement;
    3736import org.openstreetmap.josm.data.SystemOfMeasurement.SoMChangeListener;
    3837import org.openstreetmap.josm.data.coor.LatLon;
     
    5150import org.openstreetmap.josm.gui.layer.MapViewPaintable.MapViewEvent;
    5251import org.openstreetmap.josm.gui.layer.MapViewPaintable.PaintableInvalidationEvent;
    5352import org.openstreetmap.josm.gui.layer.MapViewPaintable.PaintableInvalidationListener;
     53import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
    5454import org.openstreetmap.josm.io.CachedFile;
    5555import org.openstreetmap.josm.spi.preferences.Config;
    5656import org.openstreetmap.josm.tools.ColorScale;
     
    6868     * The color that is used for drawing GPX points.
    6969     * @since 10824
    7070     */
    71     public static final NamedColorProperty DEFAULT_COLOR = new NamedColorProperty(marktr("gps point"), Color.magenta);
     71    public static final NamedColorProperty DEFAULT_COLOR_PROPERTY = new NamedColorProperty(marktr("gps point"), Color.magenta);
    7272
    7373    private final GpxData data;
    7474    private final GpxLayer layer;
     
    7878    // use alpha blending for line draw
    7979    private boolean alphaLines;
    8080    // draw direction arrows on the lines
    81     private boolean direction;
     81    private boolean arrows;
    8282    /** width of line for paint **/
    8383    private int lineWidth;
    8484    /** don't draw lines if longer than x meters **/
     
    9090    private int largesize;
    9191    private boolean hdopCircle;
    9292    /** paint direction arrow with alternate math. may be faster **/
    93     private boolean alternateDirection;
     93    private boolean arrowsFast;
    9494    /** don't draw arrows nearer to each other than this **/
    9595    private int delta;
    9696    private double minTrackDurationForTimeColoring;
     
    106106    private Color computeCacheColorUsed;
    107107    private boolean computeCacheColorDynamic;
    108108    private ColorMode computeCacheColored;
    109     private int computeCacheColorTracksTune;
     109    private int computeCacheVelocityTune;
    110110    private int computeCacheHeatMapDrawColorTableIdx;
    111111    private boolean computeCacheHeatMapDrawPointMode;
    112112    private int computeCacheHeatMapDrawGain;
     
    116116    /** Mode of the line coloring **/
    117117    private ColorMode colored;
    118118    /** max speed for coloring - allows to tweak line coloring for different speed levels. **/
    119     private int colorTracksTune;
     119    private int velocityTune;
    120120    private boolean colorModeDynamic;
    121121    private Color neutralColor;
    122122    private int largePointAlpha;
     
    127127    private ColorScale hdopScale;
    128128    private ColorScale qualityScale;
    129129    private ColorScale dateScale;
    130     private ColorScale directionScale;
     130    private ColorScale arrowScale;
    131131
    132132    /** Opacity for hdop points **/
    133133    private int hdopAlpha;
     
    145145
    146146    /** heat map parameters **/
    147147
    148     // enabled or not (override by settings)
    149     private boolean heatMapEnabled;
    150148    // draw small extra line
    151149    private boolean heatMapDrawExtraLine;
    152150    // used index for color table (parameter)
     
    199197        hdopScale = ColorScale.createHSBScale(256).makeReversed().addTitle(tr("HDOP"));
    200198        qualityScale = ColorScale.createFixedScale(rtkLibQualityColors).addTitle(tr("Quality"));
    201199        dateScale = ColorScale.createHSBScale(256).addTitle(tr("Time"));
    202         directionScale = ColorScale.createCyclicScale(256).setIntervalCount(4).addTitle(tr("Direction"));
     200        arrowScale = ColorScale.createCyclicScale(256).setIntervalCount(4).addTitle(tr("Direction"));
    203201
    204202        systemOfMeasurementChanged(null, null);
    205203    }
     
    268266        setupColors();
    269267    }
    270268
    271     private static String specName(String layerName) {
    272         return "layer " + layerName;
    273     }
    274 
    275269    /**
    276      * Get the default color for gps tracks for specified layer
    277      * @param layerName name of the GpxLayer
    278      * @param ignoreCustom do not use preferences
    279      * @return the color or null if the color is not constant
    280      */
    281     public Color getColor(String layerName, boolean ignoreCustom) {
    282         if (ignoreCustom || getColorMode(layerName) == ColorMode.NONE) {
    283             return DEFAULT_COLOR.getChildColor(
    284                     NamedColorProperty.COLOR_CATEGORY_LAYER,
    285                     layerName,
    286                     DEFAULT_COLOR.getName()).get();
    287         } else {
    288             return null;
    289         }
    290     }
    291 
    292     /**
    293270     * Read coloring mode for specified layer from preferences
    294      * @param layerName name of the GpxLayer
    295271     * @return coloring mode
    296272     */
    297     public ColorMode getColorMode(String layerName) {
     273    public ColorMode getColorMode() {
    298274        try {
    299             int i = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.colors", specName(layerName), 0);
     275            int i = optInt("colormode");
     276            if (i == -1) i = 0; //global
    300277            return ColorMode.fromIndex(i);
    301278        } catch (IndexOutOfBoundsException e) {
    302279            Logging.warn(e);
     
    304281        return ColorMode.NONE;
    305282    }
    306283
    307     /** Reads generic color from preferences (usually gray)
    308      * @return the color
    309      **/
    310     public static Color getGenericColor() {
    311         return DEFAULT_COLOR.get();
     284    private String opt(String key) {
     285        return GPXSettingsPanel.getLayerPref(layer, key);
    312286    }
    313287
     288    private boolean optBool(String key) {
     289        return Boolean.parseBoolean(opt(key));
     290    }
     291
     292    private int optInt(String key) {
     293        return GPXSettingsPanel.getLayerPrefInt(layer, key);
     294    }
     295
    314296    /**
    315297     * Read all drawing-related settings from preferences
    316      * @param layerName layer name used to access its specific preferences
    317298     **/
    318     public void readPreferences(String layerName) {
    319         String spec = specName(layerName);
    320         forceLines = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines.force", spec, false);
    321         direction = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.direction", spec, false);
    322         lineWidth = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.linewidth", spec, 0);
    323         alphaLines = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines.alpha-blend", spec, false);
     299    public void readPreferences() {
     300        forceLines = optBool("lines.force");
     301        arrows = optBool("lines.arrows");
     302        arrowsFast = optBool("lines.arrows.fast");
     303        lineWidth = optInt("lines.width");
     304        alphaLines = optBool("lines.alpha-blend");
    324305
     306        int l = optInt("lines");
    325307        if (!data.fromServer) {
    326             maxLineLength = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.max-line-length.local", spec, -1);
    327             lines = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines.local", spec, true);
     308            maxLineLength = optInt("lines.max-length.local");
     309            lines = l != 0; //draw for -1 (default), 1 (local) and 2 (all)
    328310        } else {
    329             maxLineLength = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.max-line-length", spec, 200);
    330             lines = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines", spec, true);
     311            maxLineLength = optInt("lines.max-length");
     312            lines = l == 2; //draw only for 2 (all)
    331313        }
    332         large = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.large", spec, false);
    333         largesize = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.large.size", spec, 3);
    334         hdopCircle = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.hdopcircle", spec, false);
    335         colored = getColorMode(layerName);
    336         alternateDirection = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.alternatedirection", spec, false);
    337         delta = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.min-arrow-distance", spec, 40);
    338         colorTracksTune = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.colorTracksTune", spec, 45);
    339         colorModeDynamic = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.colors.dynamic", spec, false);
     314        large = optBool("points.large");
     315        largesize = optInt("points.large.size");
     316        hdopCircle = optBool("points.hdopcircle");
     317        colored = getColorMode();
     318        delta = optInt("lines.arrows.min-distance");
     319        velocityTune = optInt("colormode.velocity.tune");
     320        colorModeDynamic = optBool("colormode.dynamic-range");
    340321        /* good HDOP's are between 1 and 3, very bad HDOP's go into 3 digit values */
    341322        hdoprange = Config.getPref().getInt("hdop.range", 7);
    342323        minTrackDurationForTimeColoring = Config.getPref().getInt("draw.rawgps.date-coloring-min-dt", 60);
     
    343324        largePointAlpha = Config.getPref().getInt("draw.rawgps.large.alpha", -1) & 0xFF;
    344325
    345326        // get heatmap parameters
    346         heatMapEnabled = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.heatmap.enabled", spec, false);
    347         heatMapDrawExtraLine = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.heatmap.line-extra", spec, false);
    348         heatMapDrawColorTableIdx = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.heatmap.colormap", spec, 0);
    349         heatMapDrawPointMode = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.heatmap.use-points", spec, false);
    350         heatMapDrawGain = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.heatmap.gain", spec, 0);
    351         heatMapDrawLowerLimit = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.heatmap.lower-limit", spec, 0);
     327        heatMapDrawExtraLine = optBool("colormode.heatmap.line-extra");
     328        heatMapDrawColorTableIdx = optInt("colormode.heatmap.colormap");
     329        heatMapDrawPointMode = optBool("colormode.heatmap.use-points");
     330        heatMapDrawGain = optInt("colormode.heatmap.gain");
     331        heatMapDrawLowerLimit = optInt("colormode.heatmap.lower-limit");
    352332
    353333        // shrink to range
    354334        heatMapDrawGain = Utils.clamp(heatMapDrawGain, -10, 10);
    355 
    356         neutralColor = getColor(layerName, true);
     335        neutralColor = DEFAULT_COLOR_PROPERTY.get();
    357336        velocityScale.setNoDataColor(neutralColor);
    358337        dateScale.setNoDataColor(neutralColor);
    359338        hdopScale.setNoDataColor(neutralColor);
    360339        qualityScale.setNoDataColor(neutralColor);
    361         directionScale.setNoDataColor(neutralColor);
     340        arrowScale.setNoDataColor(neutralColor);
    362341
    363342        largesize += lineWidth;
    364343    }
     
    368347        Bounds clipBounds = graphics.getClipBounds().getLatLonBoundsBox();
    369348        List<WayPoint> visibleSegments = listVisibleSegments(clipBounds);
    370349        if (!visibleSegments.isEmpty()) {
    371             readPreferences(layer.getName());
     350            readPreferences();
    372351            drawAll(graphics.getDefaultGraphics(), graphics.getMapView(), visibleSegments, clipBounds);
    373352            if (graphics.getMapView().getLayerManager().getActiveLayer() == layer) {
    374353                drawColorBar(graphics.getDefaultGraphics(), graphics.getMapView());
     
    462441        }
    463442
    464443        // global enabled or select via color
    465         boolean useHeatMap = heatMapEnabled || ColorMode.HEATMAP == colored;
     444        boolean useHeatMap = ColorMode.HEATMAP == colored;
    466445
    467446        // default global alpha level
    468447        float layerAlpha = 1.00f;
     
    570549            }
    571550            oldWp = null;
    572551        } else { // color mode not dynamic
    573             velocityScale.setRange(0, colorTracksTune);
     552            velocityScale.setRange(0, velocityTune);
    574553            hdopScale.setRange(0, hdoprange);
    575554            qualityScale.setRange(1, rtkLibQualityColors.length);
    576555        }
     
    594573            }
    595574            for (WayPoint trkPnt : segment) {
    596575                LatLon c = trkPnt.getCoor();
    597                 trkPnt.customColoring = neutralColor;
     576                trkPnt.customColoring = segment.getColor();
    598577                if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
    599578                    continue;
    600579                }
     
    620599                        break;
    621600                    case DIRECTION:
    622601                        double dirColor = oldWp.getCoor().bearing(trkPnt.getCoor());
    623                         color = directionScale.getColor(dirColor);
     602                        color = arrowScale.getColor(dirColor);
    624603                        break;
    625604                    case TIME:
    626605                        double t = trkPnt.getTime();
     
    642621                    }
    643622                } else { // make sure we reset outdated data
    644623                    trkPnt.drawLine = false;
    645                     color = neutralColor;
     624                    color = segment.getColor();
    646625                }
    647626                if (color != null) {
    648627                    trkPnt.customColoring = color;
     
    700679        /****************************************************************
    701680         ********** STEP 3b - DRAW NICE ARROWS **************************
    702681         ****************************************************************/
    703         if (lines && direction && !alternateDirection) {
     682        if (lines && arrows && !arrowsFast) {
    704683            Point old = null;
    705684            Point oldA = null; // last arrow painted
    706685            for (WayPoint trkPnt : visibleSegments) {
     
    730709        /****************************************************************
    731710         ********** STEP 3c - DRAW FAST ARROWS **************************
    732711         ****************************************************************/
    733         if (lines && direction && alternateDirection) {
     712        if (lines && arrows && arrowsFast) {
    734713            Point old = null;
    735714            Point oldA = null; // last arrow painted
    736715            for (WayPoint trkPnt : visibleSegments) {
     
    14801459        // CHECKSTYLE.OFF: BooleanExpressionComplexity
    14811460        if ((computeCacheMaxLineLengthUsed != maxLineLength)
    14821461                || (computeCacheColored != colored)
    1483                 || (computeCacheColorTracksTune != colorTracksTune)
     1462                || (computeCacheVelocityTune != velocityTune)
    14841463                || (computeCacheColorDynamic != colorModeDynamic)
    14851464                || (computeCacheHeatMapDrawColorTableIdx != heatMapDrawColorTableIdx)
    14861465                || (!neutralColor.equals(computeCacheColorUsed)
     
    14931472            computeCacheInSync = false;
    14941473            computeCacheColorUsed = neutralColor;
    14951474            computeCacheColored = colored;
    1496             computeCacheColorTracksTune = colorTracksTune;
     1475            computeCacheVelocityTune = velocityTune;
    14971476            computeCacheColorDynamic = colorModeDynamic;
    14981477            computeCacheHeatMapDrawColorTableIdx = heatMapDrawColorTableIdx;
    14991478            computeCacheHeatMapDrawPointMode = heatMapDrawPointMode;
     
    15291508            SystemOfMeasurement som = SystemOfMeasurement.getSystemOfMeasurement();
    15301509            velocityScale.drawColorBar(g, w-30, 50, 20, 100, som.speedValue);
    15311510        } else if (colored == ColorMode.DIRECTION) {
    1532             directionScale.drawColorBar(g, w-30, 50, 20, 100, 180.0/Math.PI);
     1511            arrowScale.drawColorBar(g, w-30, 50, 20, 100, 180.0/Math.PI);
    15331512        }
    15341513    }
    15351514
  • src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java

     
    103103        GpxLink link = new GpxLink(audioUrl.toString());
    104104        link.type = "audio";
    105105        wpt.put(GpxConstants.META_LINKS, Collections.singleton(link));
    106         wpt.addExtension("offset", Double.toString(offset));
    107         wpt.addExtension("sync-offset", Double.toString(syncOffset));
     106        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "offset", Double.toString(offset));
     107        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "sync-offset", Double.toString(syncOffset));
    108108        return wpt;
    109109    }
    110110}
  • src/org/openstreetmap/josm/gui/layer/markerlayer/DefaultMarkerProducers.java

     
    4545            return Collections.singleton(marker);
    4646        } else if (Utils.hasExtension(urlStr, "wav", "mp3", "aac", "aif", "aiff")) {
    4747            final AudioMarker audioMarker = new AudioMarker(wpt.getCoor(), wpt, url, parentLayer, time, offset);
    48             Extensions exts = (Extensions) wpt.get(GpxConstants.META_EXTENSIONS);
    49             if (exts != null && exts.containsKey("offset")) {
     48            Extensions josmExts = (Extensions) wpt.get(GpxConstants.EXTENSIONS_JOSM);
     49            if (josmExts != null && josmExts.containsKey("sync-offset")) {
    5050                try {
    51                     audioMarker.syncOffset = Double.parseDouble(exts.get("sync-offset"));
     51                    audioMarker.syncOffset = Double.parseDouble(josmExts.get("sync-offset"));
    5252                } catch (NumberFormatException nfe) {
    5353                    Logging.warn(nfe);
    5454                }
  • src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java

     
    257257        WayPoint wpt = new WayPoint(getCoor());
    258258        wpt.setTimeInMillis((long) (time * 1000));
    259259        if (text != null) {
    260             wpt.addExtension("text", text);
     260            wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "text", text);
    261261        } else if (dataProvider != null) {
    262262            for (String key : dataProvider.getTemplateKeys()) {
    263263                Object value = dataProvider.getTemplateValue(key, false);
  • src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java

     
    2020import java.util.Collection;
    2121import java.util.Comparator;
    2222import java.util.List;
     23import java.util.Optional;
    2324
    2425import javax.swing.AbstractAction;
    2526import javax.swing.Action;
     
    4849import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToPreviousMarker;
    4950import org.openstreetmap.josm.gui.layer.Layer;
    5051import org.openstreetmap.josm.gui.layer.gpx.ConvertFromMarkerLayerAction;
     52import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
    5153import org.openstreetmap.josm.io.audio.AudioPlayer;
    5254import org.openstreetmap.josm.spi.preferences.Config;
    5355import org.openstreetmap.josm.tools.ImageProvider;
     
    7577    public GpxLayer fromLayer;
    7678    private Marker currentMarker;
    7779    public AudioMarker syncAudioMarker;
     80    private Color color, realcolor;
    7881
    79     private static final Color DEFAULT_COLOR = Color.magenta;
    80     private static final NamedColorProperty COLOR_PROPERTY = new NamedColorProperty(marktr("gps marker"), DEFAULT_COLOR);
     82    /**
     83     * The default color that is used for drawing markers.
     84     */
     85    public static final NamedColorProperty DEFAULT_COLOR_PROPERTY = new NamedColorProperty(marktr("gps marker"), Color.magenta);
    8186
    8287    /**
    8388     * Constructs a new {@code MarkerLayer}.
     
    9499        double firstTime = -1.0;
    95100        String lastLinkedFile = "";
    96101
     102        Color c = null;
     103        String cs = GPXSettingsPanel.tryGetLayerPrefLocal(indata, "markers.color");
     104        if (cs != null) {
     105            try {
     106                c = Color.decode(cs);
     107            } catch (NumberFormatException ex) {
     108                Logging.warn("Could not read marker color: " + cs);
     109            }
     110        }
     111        setPrivateColors(c);
     112
    97113        for (WayPoint wpt : indata.waypoints) {
    98114            /* calculate time differences in waypoints */
    99115            double time = wpt.getTime();
     
    123139            // audio file) calculate the offset relative to the first marker of
    124140            // that group. This way the user can jump to the corresponding
    125141            // playback positions in a long audio track.
    126             Extensions exts = (Extensions) wpt.get(GpxConstants.META_EXTENSIONS);
    127             if (exts != null && exts.containsKey("offset")) {
     142            Extensions josmExts = (Extensions) wpt.get(GpxConstants.EXTENSIONS_JOSM);
     143            if (josmExts != null && josmExts.containsKey("offset")) {
    128144                try {
    129                     offset = Double.valueOf(exts.get("offset"));
     145                    offset = Double.valueOf(josmExts.get("offset"));
    130146                } catch (NumberFormatException nfe) {
    131147                    Logging.warn(nfe);
    132148                }
     
    173189    }
    174190
    175191    @Override
    176     protected NamedColorProperty getBaseColorProperty() {
    177         return COLOR_PROPERTY;
    178     }
    179 
    180     /* for preferences */
    181     public static Color getGenericColor() {
    182         return COLOR_PROPERTY.get();
    183     }
    184 
    185     @Override
    186192    public void paint(Graphics2D g, MapView mv, Bounds box) {
    187193        boolean showTextOrIcon = isTextOrIconShown();
    188         g.setColor(getColorProperty().get());
    189 
     194        g.setColor(realcolor);
    190195        if (mousePressed) {
    191196            boolean mousePressedTmp = mousePressed;
    192197            Point mousePos = mv.getMousePosition(); // Get mouse position only when necessary (it's the slowest part of marker layer painting)
     
    450455     * @return <code>true</code> if text should be shown, <code>false</code> otherwise.
    451456     */
    452457    private boolean isTextOrIconShown() {
    453         String current = Config.getPref().get("marker.show "+getName(), "show");
    454         return "show".equalsIgnoreCase(current);
     458        return Boolean.parseBoolean(GPXSettingsPanel.getLayerPref(fromLayer, "markers.show-text"));
    455459    }
    456460
     461    @Override
     462    public boolean hasColor() {
     463        return true;
     464    }
     465
     466    @Override
     467    public Color getColor() {
     468        return color;
     469    }
     470
     471    @Override
     472    public void setColor(Color color) {
     473        setPrivateColors(color);
     474        if (fromLayer != null) {
     475            String cs = null;
     476            if (color != null) {
     477                cs = String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue());
     478            }
     479            GPXSettingsPanel.putLayerPrefLocal(fromLayer, "markers.color", cs);
     480        }
     481        invalidate();
     482    }
     483
     484    private void setPrivateColors(Color color) {
     485        this.color = color;
     486        this.realcolor = Optional.ofNullable(color).orElse(DEFAULT_COLOR_PROPERTY.get());
     487    }
     488
    457489    private final class MarkerMouseAdapter extends MouseAdapter {
    458490        @Override
    459491        public void mousePressed(MouseEvent e) {
     
    503535
    504536        @Override
    505537        public void actionPerformed(ActionEvent e) {
    506             Config.getPref().put("marker.show "+layer.getName(), layer.isTextOrIconShown() ? "hide" : "show");
     538            GPXSettingsPanel.putLayerPrefLocal(layer.fromLayer, "markers.show-text", Boolean.toString(!layer.isTextOrIconShown()));
    507539            layer.invalidate();
    508540        }
    509541
  • src/org/openstreetmap/josm/gui/preferences/display/ColorPreference.java

     
    390390        PaintColors.values();
    391391        ConflictColors.getColors();
    392392        Severity.getColors();
    393         MarkerLayer.getGenericColor();
    394         GpxDrawHelper.getGenericColor();
     393        MarkerLayer.DEFAULT_COLOR_PROPERTY.get();
     394        GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get();
    395395        OsmDataLayer.getOutsideColor();
    396396        MapScaler.getColor();
    397397        MapStatus.getColors();
  • src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java

     
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55import static org.openstreetmap.josm.tools.I18n.trc;
    66
    7 import java.awt.Color;
    87import java.awt.Component;
    98import java.awt.Dimension;
    109import java.awt.GridBagLayout;
    1110import java.awt.event.ActionListener;
    1211import java.util.Enumeration;
     12import java.util.HashMap;
     13import java.util.List;
     14import java.util.Map;
     15import java.util.Optional;
    1316
    1417import javax.swing.AbstractButton;
    1518import javax.swing.BorderFactory;
     
    2225import javax.swing.JRadioButton;
    2326import javax.swing.JSlider;
    2427
     28import org.apache.commons.jcs.access.exception.InvalidArgumentException;
    2529import org.openstreetmap.josm.actions.ExpertToggleAction;
    26 import org.openstreetmap.josm.data.PreferencesUtils;
    27 import org.openstreetmap.josm.data.preferences.NamedColorProperty;
     30import org.openstreetmap.josm.data.gpx.GpxData;
    2831import org.openstreetmap.josm.gui.MainApplication;
     32import org.openstreetmap.josm.gui.layer.GpxLayer;
    2933import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper;
    3034import org.openstreetmap.josm.gui.layer.markerlayer.Marker;
    3135import org.openstreetmap.josm.gui.layer.markerlayer.Marker.TemplateEntryProperty;
     
    6771    private final JRadioButton colorTypeQuality = new JRadioButton(tr("Quality (RTKLib only, if available)"));
    6872    private final JRadioButton colorTypeTime = new JRadioButton(tr("Track date"));
    6973    private final JRadioButton colorTypeHeatMap = new JRadioButton(tr("Heat Map (dark = few, bright = many)"));
    70     private final JRadioButton colorTypeNone = new JRadioButton(tr("Single Color (can be customized for named layers)"));
     74    private final JRadioButton colorTypeNone = new JRadioButton(tr("Single Color (can be customized in the layer manager)"));
    7175    private final JRadioButton colorTypeGlobal = new JRadioButton(tr("Use global settings"));
    7276    private final JosmComboBox<String> colorTypeVelocityTune = new JosmComboBox<>(new String[] {tr("Car"), tr("Bicycle"), tr("Foot")});
    7377    private final JosmComboBox<String> colorTypeHeatMapTune = new JosmComboBox<>(new String[] {
     
    9397    private final JCheckBox useGpsAntialiasing = new JCheckBox(tr("Smooth GPX graphics (antialiasing)"));
    9498    private final JCheckBox drawLineWithAlpha = new JCheckBox(tr("Draw with Opacity (alpha blending) "));
    9599
    96     private String layerName;
    97     private final boolean local; // flag to display LocalOnly checkbox
    98     private final boolean nonlocal; // flag to display AllLines checkbox
     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
    99105
     106    private final static Map<String, Object> defaults = new HashMap<String, Object>() {{
     107        put("colormode", -1);
     108        put("colormode.dynamic-range", false);
     109        put("colormode.heatmap.colormap", 0);
     110        put("colormode.heatmap.gain", 0);
     111        put("colormode.heatmap.line-extra", false); //Einstein only
     112        put("colormode.heatmap.lower-limit", 0);
     113        put("colormode.heatmap.use-points", false);
     114        put("colormode.velocity.tune", 45);
     115        put("lines", -1);
     116        put("lines.alpha-blend", false);
     117        put("lines.arrows", false);
     118        put("lines.arrows.fast", false);
     119        put("lines.arrows.min-distance", 40);
     120        put("lines.force", false);
     121        put("lines.max-length", 200);
     122        put("lines.max-length.local", -1);
     123        put("lines.width", 0);
     124        put("markers.color", "");
     125        put("markers.show-text", true);
     126        put("points.hdopcircle", false);
     127        put("points.large", false);
     128        put("points.large.size", 3); //Einstein only
     129    }};
     130
    100131    /**
    101      * Constructs a new {@code GPXSettingsPanel} for a given layer name.
    102      * @param layerName The GPX layer name
    103      * @param local flag to display LocalOnly checkbox
    104      * @param nonlocal flag to display AllLines checkbox
     132     * Constructs a new {@code GPXSettingsPanel} for the given layers.
     133     * @param layers the GPX layers
    105134     */
    106     public GPXSettingsPanel(String layerName, boolean local, boolean nonlocal) {
     135    public GPXSettingsPanel(List<GpxLayer> layers) {
    107136        super(new GridBagLayout());
    108         this.local = local;
    109         this.nonlocal = nonlocal;
    110         this.layerName = "layer "+layerName;
     137        this.layers = layers;
     138        if (layers == null || layers.size() == 0) {
     139            throw new InvalidArgumentException("At least one layer required");
     140        }
     141        firstLayer = layers.get(0);
     142        global = false;
     143        hasLocalFile = layers.stream().anyMatch(GpxLayer::isLocalFile);
     144        hasNonLocalFile = layers.stream().anyMatch(l -> !l.isLocalFile());
    111145        initComponents();
    112146        loadPreferences();
    113147    }
     
    117151     */
    118152    public GPXSettingsPanel() {
    119153        super(new GridBagLayout());
     154        layers = null;
     155        firstLayer = null;
     156        global = hasLocalFile = hasNonLocalFile = true;
    120157        initComponents();
    121         local = false;
    122         nonlocal = false;
    123158        loadPreferences(); // preferences -> controls
    124159    }
    125160
     161    /**
     162     * Reads the preference for the given layer or the default preference if not available
     163     * @param layer the GpxLayer. Can be <code>null</code>, default preference will be returned then
     164     * @param key the drawing key to be read, without "draw.rawgps."
     165     * @return the value
     166     */
     167    public static String getLayerPref(GpxLayer layer, String key) {
     168        Object d = defaults.get(key);
     169        String ds;
     170        if (d != null) {
     171            ds = d.toString();
     172        } else {
     173            Logging.warn("No default value found for layer preference \"" + key + "\".");
     174            ds = null;
     175        }
     176        return Optional.ofNullable(tryGetLayerPrefLocal(layer, key)).orElse(Config.getPref().get("draw.rawgps." + key, ds));
     177    }
     178
     179    /**
     180     * Reads the integer preference for the given layer or the default preference if not available
     181     * @param layer the GpxLayer. Can be <code>null</code>, default preference will be returned then
     182     * @param key the drawing key to be read, without "draw.rawgps."
     183     * @return the integer value
     184     */
     185    public static int getLayerPrefInt(GpxLayer layer, String key) {
     186        try {
     187            return Integer.parseInt(getLayerPref(layer, key));
     188        } catch (NumberFormatException ex) {
     189            Object d = defaults.get(key);
     190            if (d instanceof Integer) {
     191                return (int) d;
     192            } else {
     193                Logging.warn("No valid default value found for layer preference \"" + key + "\".");
     194                return 0;
     195            }
     196        }
     197    }
     198
     199    /**
     200     * Try to read the preference for the given layer
     201     * @param layer the GpxLayer
     202     * @param key the drawing key to be read, without "draw.rawgps."
     203     * @return the value or <code>null</code> if not found
     204     */
     205    public static String tryGetLayerPrefLocal(GpxLayer layer, String key) {
     206        return layer != null ? tryGetLayerPrefLocal(layer.data, key) : null;
     207    }
     208
     209    /**
     210     * Try to read the preference for the given GpxData
     211     * @param data the GpxData
     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(GpxData data, String key) {
     216        return data != null ? data.layerPrefs.get(key) : null;
     217    }
     218
     219    /**
     220     * Puts the preference for the given layers or the default preference if layers is <code>null</code>
     221     * @param layers List of <code>GpxLayer</code> to put the drawingOptions
     222     * @param key the drawing key to be written, without "draw.rawgps."
     223     * @param value (can be <code>null</code> to remove option)
     224     */
     225    public static void putLayerPref(List<GpxLayer> layers, String key, Object value) {
     226        String v = value == null ? null : value.toString();
     227        if (layers != null) {
     228            for (GpxLayer l : layers) {
     229                putLayerPrefLocal(l.data, key, v);
     230            }
     231        } else {
     232            Config.getPref().put("draw.rawgps." + key, v);
     233        }
     234    }
     235
     236    /**
     237     * Puts the preference for the given layer
     238     * @param layer <code>GpxLayer</code> to put the drawingOptions
     239     * @param key the drawing key to be written, without "draw.rawgps."
     240     * @param value the value or <code>null</code> to remove key
     241     */
     242    public static void putLayerPrefLocal(GpxLayer layer, String key, String value) {
     243        if (layer == null) return;
     244        putLayerPrefLocal(layer.data, key, value);
     245    }
     246
     247    /**
     248     * Puts the preference for the given layer
     249     * @param data <code>GpxData</code> to put the drawingOptions. Must not be <code>null</code>
     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(GpxData data, String key, String value) {
     254        if (value == null || value.isBlank() || (defaults.get(key) != null && defaults.get(key).toString().equals(value))) {
     255            data.layerPrefs.remove(key);
     256        } else {
     257            data.layerPrefs.put(key, value);
     258        }
     259        data.setModified();
     260    }
     261
     262    private String pref(String key) {
     263        return getLayerPref(firstLayer, key);
     264    }
     265
     266    private boolean prefBool(String key) {
     267        return Boolean.parseBoolean(pref(key));
     268    }
     269
     270    private int prefInt(String key) {
     271        return getLayerPrefInt(firstLayer, key);
     272    }
     273
     274    private int prefIntLocal(String key) {
     275        try {
     276            return Integer.parseInt(tryGetLayerPrefLocal(firstLayer, key));
     277        } catch (NumberFormatException ex) {
     278            return -1;
     279        }
     280
     281    }
     282
     283    private void putPref(String key, Object value) {
     284        putLayerPref(layers, key, value);
     285    }
     286
    126287    // CHECKSTYLE.OFF: ExecutableStatementCountCheck
    127288    private void initComponents() {
    128289        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    129290
    130         // makeAutoMarkers
    131         makeAutoMarkers.setToolTipText(tr("Automatically make a marker layer from any waypoints when opening a GPX layer."));
    132         ExpertToggleAction.addVisibilitySwitcher(makeAutoMarkers);
    133         add(makeAutoMarkers, GBC.eol().insets(20, 0, 0, 5));
     291        if (global) {
     292            // makeAutoMarkers
     293            makeAutoMarkers.setToolTipText(tr("Automatically make a marker layer from any waypoints when opening a GPX layer."));
     294            ExpertToggleAction.addVisibilitySwitcher(makeAutoMarkers);
     295            add(makeAutoMarkers, GBC.eol().insets(20, 0, 0, 5));
     296        }
    134297
    135298        // drawRawGpsLines
    136299        ButtonGroup gpsLinesGroup = new ButtonGroup();
    137         if (layerName != null) {
     300        if (!global) {
    138301            gpsLinesGroup.add(drawRawGpsLinesGlobal);
    139302        }
    140303        gpsLinesGroup.add(drawRawGpsLinesNone);
     
    145308
    146309        JLabel label = new JLabel(tr("Draw lines between raw GPS points"));
    147310        add(label, GBC.eol().insets(20, 0, 0, 0));
    148         if (layerName != null) {
     311        if (!global) {
    149312            add(drawRawGpsLinesGlobal, GBC.eol().insets(40, 0, 0, 0));
    150313        }
    151314        add(drawRawGpsLinesNone, GBC.eol().insets(40, 0, 0, 0));
    152         if (layerName == null || local) {
     315        if (hasLocalFile) {
    153316            add(drawRawGpsLinesLocal, GBC.eol().insets(40, 0, 0, 0));
    154317        }
    155         if (layerName == null || nonlocal) {
     318        if (hasNonLocalFile) {
    156319            add(drawRawGpsLinesAll, GBC.eol().insets(40, 0, 0, 0));
    157320        }
    158321        ExpertToggleAction.addVisibilitySwitcher(label);
     
    242405
    243406        // colorTracks
    244407        ButtonGroup colorGroup = new ButtonGroup();
    245         if (layerName != null) {
     408        if (!global) {
    246409            colorGroup.add(colorTypeGlobal);
    247410        }
    248411        colorGroup.add(colorTypeNone);
     
    253416        colorGroup.add(colorTypeTime);
    254417        colorGroup.add(colorTypeHeatMap);
    255418
    256         colorTypeNone.setToolTipText(tr("All points and track segments will have the same color. Can be customized in Layer Manager."));
     419        colorTypeNone.setToolTipText(tr("All points and track segments will have their own color. Can be customized in Layer Manager."));
    257420        colorTypeVelocity.setToolTipText(tr("Colors points and track segments by velocity."));
    258421        colorTypeDirection.setToolTipText(tr("Colors points and track segments by direction."));
    259422        colorTypeDilution.setToolTipText(
     
    272435        add(Box.createVerticalGlue(), GBC.eol().insets(0, 20, 0, 0));
    273436
    274437        add(new JLabel(tr("Track and Point Coloring")), GBC.eol().insets(20, 0, 0, 0));
    275         if (layerName != null) {
     438        if (!global) {
    276439            add(colorTypeGlobal, GBC.eol().insets(40, 0, 0, 0));
    277440        }
    278441        add(colorTypeNone, GBC.eol().insets(40, 0, 0, 0));
     
    331494            if (null != dim) {
    332495                // get image size of environment
    333496                final int iconSize = (int) dim.getHeight();
    334                 final Color color;
    335                 // ask the GPX draw for the correct color of that layer ( if there is one )
    336                 if (null != layerName) {
    337                     color = GpxDrawHelper.DEFAULT_COLOR.getChildColor(
    338                             NamedColorProperty.COLOR_CATEGORY_LAYER, layerName, GpxDrawHelper.DEFAULT_COLOR.getName()).get();
    339                 } else {
    340                     color = GpxDrawHelper.DEFAULT_COLOR.getDefaultValue();
    341                 }
    342                 colorTypeHeatIconLabel.setIcon(GpxDrawHelper.getColorMapImageIcon(color, colorTypeHeatMapTune.getSelectedIndex(), iconSize));
     497                colorTypeHeatIconLabel.setIcon(GpxDrawHelper.getColorMapImageIcon(
     498                        GpxDrawHelper.DEFAULT_COLOR_PROPERTY.get(),
     499                        colorTypeHeatMapTune.getSelectedIndex(),
     500                        iconSize));
    343501            }
    344502        });
    345503
     
    353511        add(colorDynamic, GBC.eop().insets(40, 0, 0, 0));
    354512        ExpertToggleAction.addVisibilitySwitcher(colorDynamic);
    355513
    356         if (layerName == null) {
     514        if (global) {
    357515            // Setting waypoints for gpx layer doesn't make sense - waypoints are shown in marker layer that has different name - so show
    358516            // this only for global config
    359517
     
    363521            label.setLabelFor(waypointLabel);
    364522            add(waypointLabel, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
    365523            waypointLabel.addActionListener(e -> updateWaypointPattern(waypointLabel, waypointLabelPattern));
    366             updateWaypointLabelCombobox(waypointLabel, waypointLabelPattern, TemplateEntryProperty.forMarker(layerName));
     524            updateWaypointLabelCombobox(waypointLabel, waypointLabelPattern, TemplateEntryProperty.forMarker(null));
    367525            add(waypointLabelPattern, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 0, 0, 5));
    368526            ExpertToggleAction.addVisibilitySwitcher(label);
    369527            ExpertToggleAction.addVisibilitySwitcher(waypointLabel);
     
    379537            label.setLabelFor(audioWaypointLabel);
    380538            add(audioWaypointLabel, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
    381539            audioWaypointLabel.addActionListener(e -> updateWaypointPattern(audioWaypointLabel, audioWaypointLabelPattern));
    382             updateWaypointLabelCombobox(audioWaypointLabel, audioWaypointLabelPattern, TemplateEntryProperty.forAudioMarker(layerName));
     540            updateWaypointLabelCombobox(audioWaypointLabel, audioWaypointLabelPattern, TemplateEntryProperty.forAudioMarker(null));
    383541            add(audioWaypointLabelPattern, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 0, 0, 5));
    384542            ExpertToggleAction.addVisibilitySwitcher(label);
    385543            ExpertToggleAction.addVisibilitySwitcher(audioWaypointLabel);
     
    395553     */
    396554    public final void loadPreferences() {
    397555        makeAutoMarkers.setSelected(Config.getPref().getBoolean("marker.makeautomarkers", true));
    398         if (layerName != null && Config.getPref().get("draw.rawgps.lines."+layerName).isEmpty()
    399                 && Config.getPref().get("draw.rawgps.lines.local."+layerName).isEmpty()) {
    400             // no line preferences for layer is found
     556        int lines = global ? prefInt("lines") : prefIntLocal("lines");
     557        if (lines == 2 && hasNonLocalFile) {
     558            drawRawGpsLinesAll.setSelected(true);
     559        } else if ((lines == 1 && hasLocalFile) || (lines == -1 && global)) {
     560            drawRawGpsLinesLocal.setSelected(true);
     561        } else if (lines == 0) {
     562            drawRawGpsLinesNone.setSelected(true);
     563        } else if (lines == -1) {
    401564            drawRawGpsLinesGlobal.setSelected(true);
    402565        } else {
    403             Boolean lf = PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines.local", layerName, true);
    404             if (PreferencesUtils.getBoolean(Config.getPref(), "draw.rawgps.lines", layerName, true)) {
    405                 drawRawGpsLinesAll.setSelected(true);
    406             } else if (lf) {
    407                 drawRawGpsLinesLocal.setSelected(true);
    408             } else {
    409                 drawRawGpsLinesNone.setSelected(true);
    410             }
     566            Logging.warn("Unknown line type: " + lines);
    411567        }
    412 
    413         drawRawGpsMaxLineLengthLocal.setText(Integer.toString(PreferencesUtils.getInteger(Config.getPref(),
    414                 "draw.rawgps.max-line-length.local", layerName, -1)));
    415         drawRawGpsMaxLineLength.setText(Integer.toString(PreferencesUtils.getInteger(Config.getPref(),
    416                 "draw.rawgps.max-line-length", layerName, 200)));
    417         drawLineWidth.setText(Integer.toString(PreferencesUtils.getInteger(Config.getPref(),
    418                 "draw.rawgps.linewidth", layerName, 0)));
    419         drawLineWithAlpha.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
    420                 "draw.rawgps.lines.alpha-blend", layerName, false));
    421         forceRawGpsLines.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
    422                 "draw.rawgps.lines.force", layerName, false));
    423         drawGpsArrows.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
    424                 "draw.rawgps.direction", layerName, false));
    425         drawGpsArrowsFast.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
    426                 "draw.rawgps.alternatedirection", layerName, false));
    427         drawGpsArrowsMinDist.setText(Integer.toString(PreferencesUtils.getInteger(Config.getPref(),
    428                 "draw.rawgps.min-arrow-distance", layerName, 40)));
    429         hdopCircleGpsPoints.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
    430                 "draw.rawgps.hdopcircle", layerName, false));
    431         largeGpsPoints.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
    432                 "draw.rawgps.large", layerName, false));
     568        drawRawGpsMaxLineLengthLocal.setText(pref("lines.max-length.local"));
     569        drawRawGpsMaxLineLength.setText(pref("lines.max-length"));
     570        drawLineWidth.setText(pref("lines.width"));
     571        drawLineWithAlpha.setSelected(prefBool("lines.alpha-blend"));
     572        forceRawGpsLines.setSelected(prefBool("lines.force"));
     573        drawGpsArrows.setSelected(prefBool("lines.arrows"));
     574        drawGpsArrowsFast.setSelected(prefBool("lines.arrows.fast"));
     575        drawGpsArrowsMinDist.setText(pref("lines.arrows.min-distance"));
     576        hdopCircleGpsPoints.setSelected(prefBool("points.hdopcircle"));
     577        largeGpsPoints.setSelected(prefBool("points.large"));
    433578        useGpsAntialiasing.setSelected(Config.getPref().getBoolean("mappaint.gpx.use-antialiasing", false));
    434579
    435580        drawRawGpsLinesActionListener.actionPerformed(null);
    436 
    437         if (layerName != null && Config.getPref().get("draw.rawgps.colors."+layerName).isEmpty()) {
     581        if (!global && prefIntLocal("colormode") == -1) {
    438582            colorTypeGlobal.setSelected(true);
    439583            colorDynamic.setSelected(false);
    440584            colorDynamic.setEnabled(false);
     
    442586            colorTypeHeatMapGain.setValue(0);
    443587            colorTypeHeatMapLowerLimit.setValue(0);
    444588        } else {
    445             int colorType = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.colors", layerName, 0);
     589            int colorType = prefInt("colormode");
    446590            switch (colorType) {
    447             case 0: colorTypeNone.setSelected(true); break;
     591            case -1: case 0: colorTypeNone.setSelected(true); break;
    448592            case 1: colorTypeVelocity.setSelected(true); break;
    449593            case 2: colorTypeDilution.setSelected(true); break;
    450594            case 3: colorTypeDirection.setSelected(true); break;
     
    453597            case 6: colorTypeQuality.setSelected(true); break;
    454598            default: Logging.warn("Unknown color type: " + colorType);
    455599            }
    456             int ccts = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.colorTracksTune", layerName, 45);
     600            int ccts = prefInt("colormode.velocity.tune");
    457601            colorTypeVelocityTune.setSelectedIndex(ccts == 10 ? 2 : (ccts == 20 ? 1 : 0));
    458             colorTypeHeatMapTune.setSelectedIndex(PreferencesUtils.getInteger(Config.getPref(),
    459                     "draw.rawgps.heatmap.colormap", layerName, 0));
    460             colorDynamic.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
    461                     "draw.rawgps.colors.dynamic", layerName, false));
    462             colorTypeHeatMapPoints.setSelected(PreferencesUtils.getBoolean(Config.getPref(),
    463                     "draw.rawgps.heatmap.use-points", layerName, false));
    464             colorTypeHeatMapGain.setValue(PreferencesUtils.getInteger(Config.getPref(),
    465                     "draw.rawgps.heatmap.gain", layerName, 0));
    466             colorTypeHeatMapLowerLimit.setValue(PreferencesUtils.getInteger(Config.getPref(),
    467                     "draw.rawgps.heatmap.lower-limit", layerName, 0));
     602            colorTypeHeatMapTune.setSelectedIndex(prefInt("colormode.heatmap.colormap"));
     603            colorDynamic.setSelected(prefBool("colormode.dynamic-range"));
     604            colorTypeHeatMapPoints.setSelected(prefBool("colormode.heatmap.use-points"));
     605            colorTypeHeatMapGain.setValue(prefInt("colormode.heatmap.gain"));
     606            colorTypeHeatMapLowerLimit.setValue(prefInt("colormode.heatmap.lower-limit"));
    468607        }
    469608    }
    470609
    471610    /**
    472      * Save preferences from UI controls, globally or for a specified layer.
    473      * @param layerName The GPX layer name. Can be {@code null}, in that case, global preferences are written
    474      * @param locLayer {@code true} if the GPX layer is a local one. Ignored if {@code layerName} is null
     611     * Save preferences from UI controls, globally or for the specified layers.
    475612     * @return {@code true} when restart is required, {@code false} otherwise
    476613     */
    477     public boolean savePreferences(String layerName, boolean locLayer) {
    478         String layerNameDot = ".layer "+layerName;
    479         if (layerName == null) {
    480             layerNameDot = "";
     614    public boolean savePreferences() {
     615        if (global) {
     616            Config.getPref().putBoolean("marker.makeautomarkers", makeAutoMarkers.isSelected());
    481617        }
    482         Config.getPref().putBoolean("marker.makeautomarkers"+layerNameDot, makeAutoMarkers.isSelected());
    483         if (drawRawGpsLinesGlobal.isSelected()) {
    484             Config.getPref().put("draw.rawgps.lines" + layerNameDot, null);
    485             Config.getPref().put("draw.rawgps.max-line-length" + layerNameDot, null);
    486             Config.getPref().put("draw.rawgps.lines.local" + layerNameDot, null);
    487             Config.getPref().put("draw.rawgps.max-line-length.local" + layerNameDot, null);
    488             Config.getPref().put("draw.rawgps.lines.force"+layerNameDot, null);
    489             Config.getPref().put("draw.rawgps.direction"+layerNameDot, null);
    490             Config.getPref().put("draw.rawgps.alternatedirection"+layerNameDot, null);
    491             Config.getPref().put("draw.rawgps.min-arrow-distance"+layerNameDot, null);
     618        if (!global && drawRawGpsLinesGlobal.isSelected()) {
     619            putPref("lines", null);
     620            putPref("lines.max-length", null);
     621            putPref("lines.max-length.local", null);
     622            putPref("lines.force", null);
     623            putPref("lines.arrows", null);
     624            putPref("lines.arrows.fast", null);
     625            putPref("lines.arrows.min-distance", null);
    492626        } else {
    493             if (layerName == null || !locLayer) {
    494                 Config.getPref().putBoolean("draw.rawgps.lines" + layerNameDot, drawRawGpsLinesAll.isSelected());
    495                 Config.getPref().put("draw.rawgps.max-line-length" + layerNameDot, drawRawGpsMaxLineLength.getText());
     627            if (drawRawGpsLinesNone.isSelected()) {
     628                putPref("lines", 0);
     629            } else if (drawRawGpsLinesLocal.isSelected()) {
     630                putPref("lines", 1);
     631            } else if (drawRawGpsLinesAll.isSelected()) {
     632                putPref("lines", 2);
    496633            }
    497             if (layerName == null || locLayer) {
    498                 Config.getPref().putBoolean("draw.rawgps.lines.local" + layerNameDot,
    499                         drawRawGpsLinesAll.isSelected() || drawRawGpsLinesLocal.isSelected());
    500                 Config.getPref().put("draw.rawgps.max-line-length.local" + layerNameDot,
    501                         drawRawGpsMaxLineLengthLocal.getText());
    502             }
    503             Config.getPref().putBoolean("draw.rawgps.lines.force"+layerNameDot, forceRawGpsLines.isSelected());
    504             Config.getPref().putBoolean("draw.rawgps.direction"+layerNameDot, drawGpsArrows.isSelected());
    505             Config.getPref().putBoolean("draw.rawgps.alternatedirection"+layerNameDot, drawGpsArrowsFast.isSelected());
    506             Config.getPref().put("draw.rawgps.min-arrow-distance"+layerNameDot, drawGpsArrowsMinDist.getText());
     634            putPref("lines.max-length", drawRawGpsMaxLineLength.getText());
     635            putPref("lines.max-length.local", drawRawGpsMaxLineLengthLocal.getText());
     636            putPref("lines.force", forceRawGpsLines.isSelected());
     637            putPref("lines.arrows", drawGpsArrows.isSelected());
     638            putPref("lines.arrows.fast", drawGpsArrowsFast.isSelected());
     639            putPref("lines.arrows.min-distance", drawGpsArrowsMinDist.getText());
    507640        }
    508641
    509         Config.getPref().putBoolean("draw.rawgps.hdopcircle"+layerNameDot, hdopCircleGpsPoints.isSelected());
    510         Config.getPref().putBoolean("draw.rawgps.large"+layerNameDot, largeGpsPoints.isSelected());
    511         Config.getPref().put("draw.rawgps.linewidth"+layerNameDot, drawLineWidth.getText());
    512         Config.getPref().putBoolean("draw.rawgps.lines.alpha-blend"+layerNameDot, drawLineWithAlpha.isSelected());
     642        putPref("points.hdopcircle", hdopCircleGpsPoints.isSelected());
     643        putPref("points.large", largeGpsPoints.isSelected());
     644        putPref("lines.width", drawLineWidth.getText());
     645        putPref("lines.alpha-blend", drawLineWithAlpha.isSelected());
    513646
    514647        Config.getPref().putBoolean("mappaint.gpx.use-antialiasing", useGpsAntialiasing.isSelected());
    515648
    516         TemplateEntryProperty.forMarker(layerName).put(waypointLabelPattern.getText());
    517         TemplateEntryProperty.forAudioMarker(layerName).put(audioWaypointLabelPattern.getText());
     649        TemplateEntryProperty.forMarker(null).put(waypointLabelPattern.getText());
     650        TemplateEntryProperty.forAudioMarker(null).put(audioWaypointLabelPattern.getText());
    518651
    519652        if (colorTypeGlobal.isSelected()) {
    520             Config.getPref().put("draw.rawgps.colors"+layerNameDot, null);
    521             Config.getPref().put("draw.rawgps.colors.dynamic"+layerNameDot, null);
    522             Config.getPref().put("draw.rawgps.colorTracksTunec"+layerNameDot, null);
     653            putPref("colormode", null);
     654            putPref("colormode.dynamic-range", null);
     655            putPref("colormode.velocity.tune", null);
    523656            return false;
    524657        } else if (colorTypeVelocity.isSelected()) {
    525             Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 1);
     658            putPref("colormode", 1);
    526659        } else if (colorTypeDilution.isSelected()) {
    527             Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 2);
     660            putPref("colormode", 2);
    528661        } else if (colorTypeDirection.isSelected()) {
    529             Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 3);
     662            putPref("colormode", 3);
    530663        } else if (colorTypeTime.isSelected()) {
    531             Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 4);
     664            putPref("colormode", 4);
    532665        } else if (colorTypeHeatMap.isSelected()) {
    533             Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 5);
     666            putPref("colormode", 5);
    534667        } else if (colorTypeQuality.isSelected()) {
    535             Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 6);
     668            putPref("colormode", 6);
    536669        } else {
    537             Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 0);
     670            putPref("colormode", 0);
    538671        }
    539         Config.getPref().putBoolean("draw.rawgps.colors.dynamic"+layerNameDot, colorDynamic.isSelected());
     672        putPref("colormode.dynamic-range", colorDynamic.isSelected());
    540673        int ccti = colorTypeVelocityTune.getSelectedIndex();
    541         Config.getPref().putInt("draw.rawgps.colorTracksTune"+layerNameDot, ccti == 2 ? 10 : (ccti == 1 ? 20 : 45));
    542         Config.getPref().putInt("draw.rawgps.heatmap.colormap"+layerNameDot, colorTypeHeatMapTune.getSelectedIndex());
    543         Config.getPref().putBoolean("draw.rawgps.heatmap.use-points"+layerNameDot, colorTypeHeatMapPoints.isSelected());
    544         Config.getPref().putInt("draw.rawgps.heatmap.gain"+layerNameDot, colorTypeHeatMapGain.getValue());
    545         Config.getPref().putInt("draw.rawgps.heatmap.lower-limit"+layerNameDot, colorTypeHeatMapLowerLimit.getValue());
     674        putPref("colormode.velocity.tune", ccti == 2 ? 10 : (ccti == 1 ? 20 : 45));
     675        putPref("colormode.heatmap.colormap", colorTypeHeatMapTune.getSelectedIndex());
     676        putPref("colormode.heatmap.use-points", colorTypeHeatMapPoints.isSelected());
     677        putPref("colormode.heatmap.gain", colorTypeHeatMapGain.getValue());
     678        putPref("colormode.heatmap.lower-limit", colorTypeHeatMapLowerLimit.getValue());
    546679
    547680        return false;
    548681    }
    549682
    550     /**
    551      * Save preferences from UI controls for initial layer or globally
    552      * @return {@code true} when restart is required, {@code false} otherwise
    553      */
    554     public boolean savePreferences() {
    555         return savePreferences(null, false);
    556     }
    557 
    558683    private static void updateWaypointLabelCombobox(JosmComboBox<String> cb, JosmTextField tf, TemplateEntryProperty property) {
    559684        String labelPattern = property.getAsString();
    560685        boolean found = false;
  • src/org/openstreetmap/josm/io/GpxReader.java

     
    5353        RTE,
    5454        TRK,
    5555        EXT,
     56        JOSMPREFS,
    5657        AUTHOR,
    5758        LINK,
    5859        TRKSEG,
     
    7677        private State currentState = State.INIT;
    7778
    7879        private GpxLink currentLink;
    79         private Extensions currentExtensions;
     80        private Map<String, String> currentExtensionMap = new HashMap<>();
    8081        private Stack<State> states;
    8182        private final Stack<String> elements = new Stack<>();
    8283
     
    159160                case "extensions":
    160161                    states.push(currentState);
    161162                    currentState = State.EXT;
    162                     currentExtensions = new Extensions();
    163163                    break;
    164164                case "gpx":
    165165                    if (atts.getValue("creator") != null && atts.getValue("creator").startsWith("Nokia Sports Tracker")) {
     
    178178                case "extensions":
    179179                    states.push(currentState);
    180180                    currentState = State.EXT;
    181                     currentExtensions = new Extensions();
    182181                    break;
    183182                case "copyright":
    184183                    states.push(currentState);
     
    228227                case "extensions":
    229228                    states.push(currentState);
    230229                    currentState = State.EXT;
    231                     currentExtensions = new Extensions();
    232230                    break;
    233231                default: // Do nothing
    234232                }
     
    250248                case "extensions":
    251249                    states.push(currentState);
    252250                    currentState = State.EXT;
    253                     currentExtensions = new Extensions();
    254251                    break;
    255252                default: // Do nothing
    256253                }
     
    270267                case "extensions":
    271268                    states.push(currentState);
    272269                    currentState = State.EXT;
    273                     currentExtensions = new Extensions();
    274270                    break;
    275271                default: // Do nothing
    276272                }
    277273                break;
     274            case EXT:
     275                if ("layerPreferences".equals(localName)) {
     276                    states.push(currentState);
     277                    currentState = State.JOSMPREFS;
     278                }
     279                break;
     280            case JOSMPREFS:
     281                String k, v;
     282                if ("entry".equals(localName) && (k = atts.getValue("key")) != null && (v = atts.getValue("value")) != null) {
     283                    data.layerPrefs.put(k, v);
     284                }
     285                break;
    278286            default: // Do nothing
    279287            }
    280288            accumulator.setLength(0);
     
    349357                    if ((currentState == State.METADATA && "metadata".equals(localName)) ||
    350358                        (currentState == State.GPX && "gpx".equals(localName))) {
    351359                        convertUrlToLink(data.attr);
    352                         if (currentExtensions != null && !currentExtensions.isEmpty()) {
    353                             data.put(META_EXTENSIONS, currentExtensions);
    354                         }
     360                        data.addExtensions(currentExtensionMap);
    355361                        currentState = states.pop();
    356362                    }
    357363                    break;
     
    464470                case "wpt":
    465471                    currentState = states.pop();
    466472                    convertUrlToLink(currentWayPoint.attr);
    467                     if (currentExtensions != null && !currentExtensions.isEmpty()) {
    468                         currentWayPoint.put(META_EXTENSIONS, currentExtensions);
    469                     }
     473                    currentWayPoint.addExtensions(currentExtensionMap);
    470474                    data.waypoints.add(currentWayPoint);
     475                    currentExtensionMap = new HashMap<>();
    471476                    break;
    472477                default: // Do nothing
    473478                }
     
    483488                case "trk":
    484489                    currentState = states.pop();
    485490                    convertUrlToLink(currentTrackAttr);
    486                     data.addTrack(new ImmutableGpxTrack(currentTrack, currentTrackAttr));
     491                    ImmutableGpxTrack trk = new ImmutableGpxTrack(currentTrack, currentTrackAttr, currentExtensionMap);
     492                    data.addTrack(trk);
     493                    currentExtensionMap = new HashMap<>();
    487494                    break;
    488495                case "name":
    489496                case "cmt":
     
    499506                }
    500507                break;
    501508            case EXT:
     509                String acc;
    502510                if ("extensions".equals(localName)) {
    503511                    currentState = states.pop();
    504                 } else if (JOSM_EXTENSIONS_NAMESPACE_URI.equals(namespaceURI)) {
    505                     // only interested in extensions written by JOSM
    506                     currentExtensions.put(localName, accumulator.toString());
     512                } else if ((acc = accumulator.toString().trim()).length() > 0) {
     513                    currentExtensionMap.put(qName, acc);
    507514                }
    508515                break;
     516            case JOSMPREFS:
     517                if ("layerPreferences".equals(localName)) {
     518                    currentState = states.pop();
     519                }
     520                break;
    509521            default:
    510522                switch (localName) {
    511523                case "wpt":
     
    519531                default: // Do nothing
    520532                }
    521533            }
     534            accumulator.setLength(0);
    522535        }
    523536
    524537        @Override
     
    525538        public void endDocument() throws SAXException {
    526539            if (!states.empty())
    527540                throw new SAXException(tr("Parse error: invalid document structure for GPX document."));
    528             Extensions metaExt = (Extensions) data.get(META_EXTENSIONS);
    529             if (metaExt != null && "true".equals(metaExt.get("from-server"))) {
    530                 data.fromServer = true;
     541            Extensions josmMetaExt = (Extensions) data.get(EXTENSIONS_JOSM);
     542            if (josmMetaExt != null) {
     543                if ("true".equals(josmMetaExt.get("from-server"))) {
     544                    data.fromServer = true;
     545                }
    531546            }
    532547            gpxData = data;
    533548        }
  • src/org/openstreetmap/josm/io/GpxWriter.java

     
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.awt.Color;
    67import java.io.BufferedWriter;
    78import java.io.OutputStream;
    89import java.io.OutputStreamWriter;
    910import java.io.PrintWriter;
    1011import java.nio.charset.StandardCharsets;
     12import java.util.AbstractMap.SimpleEntry;
     13import java.util.ArrayList;
    1114import java.util.Collection;
    1215import java.util.Date;
     16import java.util.HashMap;
    1317import java.util.List;
    1418import java.util.Map;
    1519import java.util.Map.Entry;
     
    5963    private static final int ROUTE_POINT = 1;
    6064    private static final int TRACK_POINT = 2;
    6165
     66    private HashMap<String, Entry<String, String>> extlinks = new HashMap<>();
     67
    6268    /**
    6369     * Writes the given GPX data.
    6470     * @param data The data to write
    6571     */
    6672    public void write(GpxData data) {
     73        write(data, EXTENSIONS_DRAWING, true);
     74    }
     75
     76    /**
     77     * Writes the given GPX data.
     78     *
     79     * @param data The data to write
     80     * @param colorFormat determines if colors are saved and which extension is to be used, can be
     81     * {@link GpxConstants#EXTENSIONS_GARMIN}, {@link GpxConstants#EXTENSIONS_DRAWING} or <code>null</code>
     82     * @param savePrefs whether layer specific preferences are saved
     83     */
     84    public void write(GpxData data, String colorFormat, boolean savePrefs) {
    6785        this.data = data;
    6886        // We write JOSM specific meta information into gpx 'extensions' elements.
    6987        // In particular it is noted whether the gpx data is from the OSM server
     
    7189        // and some extra synchronization info for export of AudioMarkers.
    7290        // It is checked in advance, if any extensions are used, so we know whether
    7391        // a namespace declaration is necessary.
    74         boolean hasExtensions = data.fromServer;
    75         if (!hasExtensions) {
     92
     93        boolean hasJosmExtension = data.fromServer || !data.layerPrefs.isEmpty(),
     94                hasGpxxExtension = false,
     95                hasGpxdExtension = false,
     96                searchGpxx = EXTENSIONS_GARMIN.equals(colorFormat),
     97                searchGpxd = EXTENSIONS_DRAWING.equals(colorFormat);
     98
     99        if (!hasJosmExtension) {
    76100            for (WayPoint wpt : data.waypoints) {
    77                 Extensions extensions = (Extensions) wpt.get(META_EXTENSIONS);
     101                Extensions extensions = (Extensions) wpt.get(EXTENSIONS_JOSM);
    78102                if (extensions != null && !extensions.isEmpty()) {
    79                     hasExtensions = true;
     103                    hasJosmExtension = true;
    80104                    break;
    81105                }
    82106            }
    83107        }
    84108
     109        if (searchGpxx || searchGpxd) {
     110
     111            HashMap<Color, String> closestColorCache = new HashMap<>();
     112
     113            for (GpxTrack trk : data.getTracks()) {
     114                Extensions gpxx = (Extensions) trk.get(EXTENSIONS_GARMIN),
     115                           gpxd = (Extensions) trk.get(EXTENSIONS_DRAWING);
     116                String gpxxC = null, gpxdC = null;
     117
     118                if (gpxd != null) {
     119                    gpxdC = gpxd.get("color");
     120                }
     121                if (gpxx != null) {
     122                    gpxxC = gpxx.get("displaycolor");
     123                }
     124
     125                if (searchGpxd && gpxdC == null && gpxx != null && gpxxC != null) {
     126                    //Convert GPXX to GPXD
     127                    Color c = GARMIN_COLORS.get(gpxxC);
     128                    if (c != null) {
     129                        //Remove GPXX
     130                        trk.removeExtensionKey(EXTENSIONS_GARMIN, "displaycolor");
     131                        //Put GPXD
     132                        trk.setColor(c);
     133                        hasGpxdExtension = true;
     134                    } else {
     135                        Logging.warn("Could not read garmin color: " + gpxxC);
     136                    }
     137                } else if (searchGpxx && gpxxC == null && gpxd != null && gpxdC != null) {
     138                    //Convert GPXD to GPXX
     139                    try {
     140                        Color c = Color.decode(gpxdC);
     141                        //Remove GPXD
     142                        trk.removeExtensionKey(EXTENSIONS_DRAWING, "color");
     143                        //Put GPXX
     144                        String colorString = null;
     145                        double closestDiff = -1;
     146
     147                        if (closestColorCache.containsKey(c)) {
     148                            colorString = closestColorCache.get(c);
     149                        } else {
     150                            //find closest garmin color
     151                            for (Entry<String, Color> e : GARMIN_COLORS.entrySet()) {
     152                                double diff = colorDist(e.getValue(), c);
     153                                if (closestDiff < 0 || diff < closestDiff) {
     154                                    colorString = e.getKey();
     155                                    closestDiff = diff;
     156                                    if (closestDiff == 0) break;
     157                                }
     158                            }
     159                            closestColorCache.put(c, colorString);
     160                        }
     161                        trk.addExtensionKey(EXTENSIONS_GARMIN, "DisplayColor", colorString);
     162                        hasGpxxExtension = true;
     163                    } catch (NumberFormatException ex) {
     164                        Logging.warn("Could not read gpxd color: " + gpxdC);
     165                    }
     166                }
     167                //Must be checked again because of conversion above
     168                if (!hasGpxdExtension && gpxd != null && !gpxd.isEmpty()) {
     169                    hasGpxdExtension = true;
     170                }
     171                if (!hasGpxxExtension && gpxx != null && !gpxx.isEmpty()) {
     172                    hasGpxxExtension = true;
     173                }
     174            }
     175        }
     176
    85177        out.println("<?xml version='1.0' encoding='UTF-8'?>");
    86178        out.println("<gpx version=\"1.1\" creator=\"JOSM GPX export\" xmlns=\"http://www.topografix.com/GPX/1/1\"");
    87         out.println((hasExtensions ? String.format("    xmlns:josm=\"%s\"%n", JOSM_EXTENSIONS_NAMESPACE_URI) : "") +
    88                     "    xmlns:xsi=\""+XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI+"\"");
    89         out.println("    xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">");
     179
     180        String schemaLocations = "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd";
     181
     182        if (hasJosmExtension) {
     183            extlinks.put(GpxConstants.EXTENSIONS_JOSM, new SimpleEntry<>(GpxConstants.XML_URI_EXTENSIONS_JOSM, GpxConstants.XML_XSD_EXTENSIONS_JOSM));
     184        }
     185        if (hasGpxdExtension) {
     186            extlinks.put(GpxConstants.EXTENSIONS_DRAWING, new SimpleEntry<>(GpxConstants.XML_URI_EXTENSIONS_DRAWING, GpxConstants.XML_XSD_EXTENSIONS_DRAWING));
     187        }
     188        if (hasGpxxExtension) {
     189            extlinks.put(GpxConstants.EXTENSIONS_GARMIN, new SimpleEntry<>(GpxConstants.XML_URI_EXTENSIONS_GARMIN, GpxConstants.XML_XSD_EXTENSIONS_GARMIN));
     190        }
     191
     192        for (Entry<String, Entry<String, String>> e : extlinks.entrySet()) {
     193            String k = e.getKey();
     194            Entry<String, String> v = e.getValue();
     195            if (!k.startsWith(EXTENSIONS_PREFIX)) {
     196                throw new Extensions.InvalidExtensionException();
     197            }
     198            out.println(String.format("    xmlns:%s=\"%s\"", k.substring(k.lastIndexOf(".") + 1), v.getKey()));
     199            schemaLocations += " " + v.getKey() + " " + v.getValue();
     200        }
     201
     202        out.println("    xmlns:xsi=\""+XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI+"\"");
     203        out.println(String.format("    xsi:schemaLocation=\"%s\">", schemaLocations));
    90204        indent = "  ";
    91         writeMetaData();
     205        writeMetaData(savePrefs);
    92206        writeWayPoints();
    93207        writeRoutes();
    94208        writeTracks();
     
    97211    }
    98212
    99213    private void writeAttr(IWithAttributes obj, List<String> keys) {
     214        List<Extensions> allExtensions = new ArrayList<>();
    100215        for (String key : keys) {
    101216            if (META_LINKS.equals(key)) {
    102217                Collection<GpxLink> lValue = obj.<GpxLink>getCollection(key);
     
    105220                        gpxLink(link);
    106221                    }
    107222                }
    108             } else if (META_EXTENSIONS.equals(key)) {
    109                 Extensions extensions = (Extensions) obj.get(key);
    110                 if (extensions != null) {
    111                     gpxExtensions(extensions);
     223            } else if (key != null && key.startsWith(EXTENSIONS_PREFIX)) {
     224                Extensions ex = (Extensions) obj.get(key);
     225                if (ex != null && !ex.isEmpty()) {
     226                    //ex.setType(key);
     227                    allExtensions.add(ex);
    112228                }
    113229            } else {
    114230                String value = obj.getString(key);
     
    126242                }
    127243            }
    128244        }
     245        gpxExtensions(allExtensions);
    129246    }
    130247
    131     private void writeMetaData() {
     248    double colorDist(Color c1, Color c2) {
     249        // Simple Euclidean distance between two colors
     250        return Math.sqrt(Math.pow(c1.getRed() - c2.getRed(), 2)
     251                + Math.pow(c1.getGreen() - c2.getGreen(), 2)
     252                + Math.pow(c1.getBlue() - c2.getBlue(), 2));
     253    }
     254
     255
     256    private void writeMetaData(boolean savePrefs) {
    132257        Map<String, Object> attr = data.attr;
    133258        openln("metadata");
    134259
     
    147272            if (attr.containsKey(META_AUTHOR_EMAIL)) {
    148273                String[] tmp = data.getString(META_AUTHOR_EMAIL).split("@");
    149274                if (tmp.length == 2) {
    150                     inline("email", "id=\"" + tmp[0] + "\" domain=\""+tmp[1]+'\"');
     275                    inline("email", "id=\"" + encode(tmp[0]) + "\" domain=\"" + encode(tmp[1]) +'\"');
    151276                }
    152277            }
    153278            // write the author link
     
    158283        // write the copyright details
    159284        if (attr.containsKey(META_COPYRIGHT_LICENSE)
    160285                || attr.containsKey(META_COPYRIGHT_YEAR)) {
    161             openAtt("copyright", "author=\""+ data.get(META_COPYRIGHT_AUTHOR) +'\"');
     286            openAtt("copyright", "author=\""+ encode(data.get(META_COPYRIGHT_AUTHOR).toString()) +'\"');
    162287            if (attr.containsKey(META_COPYRIGHT_YEAR)) {
    163288                simpleTag("year", (String) data.get(META_COPYRIGHT_YEAR));
    164289            }
     
    187312            inline("bounds", b);
    188313        }
    189314
    190         if (data.fromServer) {
     315        if (data.fromServer || (savePrefs && !data.layerPrefs.isEmpty())) {
    191316            openln("extensions");
    192             simpleTag("josm:from-server", "true");
     317
     318            if (data.fromServer) {
     319                simpleTag("josm:from-server", "true");
     320            }
     321
     322
     323            if (!data.layerPrefs.isEmpty()) {
     324                openln("josm:layerPreferences");
     325                ArrayList<Entry<String, String>> prefs = new ArrayList<>(data.layerPrefs.entrySet());
     326                prefs.sort((o1, o2) -> o1.getKey().compareTo(o2.getKey()));
     327                for (Entry<String, String> pref : prefs) {
     328                    inline("josm:entry", "key=\"" + encode(pref.getKey()) + "\" value=\"" + encode(pref.getValue()) + "\"");
     329                }
     330                closeln("josm:layerPreferences");
     331            }
     332
    193333            closeln("extensions");
    194334        }
    195335
     
    278418     */
    279419    private void gpxLink(GpxLink link) {
    280420        if (link != null) {
    281             openAtt("link", "href=\"" + link.uri + '\"');
     421            openAtt("link", "href=\"" + encode(link.uri) + '\"');
    282422            simpleTag("text", link.text);
    283423            simpleTag("type", link.type);
    284424            closeln("link");
     
    318458        }
    319459    }
    320460
    321     private void gpxExtensions(Extensions extensions) {
    322         if (extensions != null && !extensions.isEmpty()) {
     461    private void gpxExtensions(List<Extensions> allExtensions) {
     462        if (!allExtensions.isEmpty()) {
    323463            openln("extensions");
    324             for (Entry<String, String> e : extensions.entrySet()) {
    325                 simpleTag("josm:" + e.getKey(), e.getValue());
     464            for (Extensions extensions : allExtensions) {
     465                if (extlinks.containsKey(extensions.getType())) {
     466                    //making sure links were actually added / filter if no colors are to be saved at all.
     467                    //TODO: probably makes more sense to do the conversion here instead of in the beginning
     468                    //note that the colors will actually change during the conversion to GPXX
     469                    boolean garmin = EXTENSIONS_GARMIN.equals(extensions.getType());
     470                    if (garmin) { //allow nested Garmin TrackExtension. Not ideal but does the job.
     471                        openln("gpxx:TrackExtension");
     472                    }
     473                    for (Entry<String, String> e : extensions.entrySet()) {
     474                        simpleTag(extensions.getPrefix() + e.getKey(), e.getValue());
     475                    }
     476                    if (garmin) {
     477                        closeln("gpxx:TrackExtension");
     478                    }
     479                }
    326480            }
    327481            closeln("extensions");
    328482        }
  • test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java

     
    473473    public void testEqualsContract() {
    474474        TestUtils.assumeWorkingEqualsVerifier();
    475475        EqualsVerifier.forClass(GpxData.class).usingGetClass()
    476             .withIgnoredFields("attr", "creator", "fromServer", "storageFile", "listeners", "tracks", "routes", "waypoints", "proxy", "segSpans")
     476            .withIgnoredFields("attr", "creator", "fromServer", "storageFile", "listeners", "tracks", "routes", "waypoints", "proxy", "segSpans", "modified")
    477477            .withPrefabValues(WayPoint.class, new WayPoint(LatLon.NORTH_POLE), new WayPoint(LatLon.SOUTH_POLE))
    478478            .withPrefabValues(ListenerList.class, ListenerList.create(), ListenerList.create())
    479479            .verify();
  • test/unit/org/openstreetmap/josm/data/gpx/ImmutableGpxTrackTest.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.gpx;
    33
     4import static org.junit.Assert.assertEquals;
     5import static org.junit.Assert.assertNull;
     6
     7import java.awt.Color;
     8import java.util.ArrayList;
     9import java.util.HashMap;
     10import java.util.Map;
     11
    412import org.junit.Rule;
    513import org.junit.Test;
    614import org.openstreetmap.josm.TestUtils;
    715import org.openstreetmap.josm.testutils.JOSMTestRules;
     16import org.openstreetmap.josm.tools.ListenerList;
    817
    918import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    1019import nl.jqno.equalsverifier.EqualsVerifier;
     
    2332    public JOSMTestRules test = new JOSMTestRules();
    2433
    2534    /**
     35     * Tests weather the track can read and write colors.
     36     */
     37    @Test
     38    public void testColors() {
     39        GpxTrack trk = new ImmutableGpxTrack(new ArrayList<GpxTrackSegment>(), new HashMap<>());
     40        trk.addExtensions(Map.of("gpxd:color", "#FF0000"));
     41        assertEquals(trk.getColor(), Color.RED);
     42        trk.addExtensionKey(GpxConstants.EXTENSIONS_DRAWING, "color", "#00FF00");
     43        assertEquals(trk.getColor(), Color.GREEN);
     44        trk.removeExtensionKey(GpxConstants.EXTENSIONS_DRAWING, "color");
     45        assertNull(trk.getColor());
     46        trk.addExtensionKey(GpxConstants.EXTENSIONS_GARMIN, "DisplayColor", "Blue");
     47        assertEquals(trk.getColor(), Color.BLUE);
     48        trk.setColor(null);
     49        assertNull(trk.getColor());
     50        trk.addExtensions(Map.of("gpxx:DisplayColor", "Cyan"));
     51        assertEquals(trk.getColor(), Color.CYAN);
     52        trk.setColor(Color.YELLOW);
     53        assertEquals(trk.getColor(), Color.YELLOW);
     54    }
     55
     56    /**
    2657     * Unit test of methods {@link ImmutableGpxTrack#equals} and {@link ImmutableGpxTrack#hashCode}.
    2758     */
    2859    @Test
     
    3061        TestUtils.assumeWorkingEqualsVerifier();
    3162        EqualsVerifier.forClass(ImmutableGpxTrack.class).usingGetClass()
    3263            .suppress(Warning.NONFINAL_FIELDS)
    33             .withIgnoredFields("bounds", "length")
     64            .withPrefabValues(ListenerList.class, ListenerList.create(), ListenerList.create())
     65            .withIgnoredFields("bounds", "length", "colorCache", "listeners")
    3466            .verify();
    3567    }
    3668}
  • test/unit/org/openstreetmap/josm/gui/layer/GpxLayerTest.java

     
    33
    44import static org.junit.Assert.assertEquals;
    55import static org.junit.Assert.assertFalse;
     6import static org.junit.Assert.assertNull;
    67import static org.junit.Assert.assertTrue;
    78
    89import java.awt.Color;
     
    1011import java.util.ArrayList;
    1112import java.util.Collection;
    1213import java.util.HashMap;
     14import java.util.Map;
    1315import java.util.TimeZone;
    1416
    1517import javax.swing.JScrollPane;
     
    1820import org.junit.Test;
    1921import org.openstreetmap.josm.TestUtils;
    2022import org.openstreetmap.josm.data.gpx.GpxData;
     23import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
    2124import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
    2225import org.openstreetmap.josm.data.gpx.WayPoint;
    2326import org.openstreetmap.josm.data.osm.DataSet;
     
    7376    @Test
    7477    public void testGpxLayer() throws Exception {
    7578        GpxLayer layer = new GpxLayer(new GpxData(), "foo", false);
     79        ImmutableGpxTrack trk = new ImmutableGpxTrack(new ArrayList<GpxTrackSegment>(), new HashMap<>());
     80        trk.addExtensions(Map.of("gpxd:color", "#FF0000"));
     81        layer.data.addTrack(trk);
     82
    7683        assertEquals("foo", layer.getName());
    7784        assertFalse(layer.isLocalFile());
    78         assertEquals(Color.MAGENTA, layer.getColorProperty().get());
    79         assertEquals("<html>0 tracks (0 segments), 0 routes, 0 waypoints<br>Length: < 0.01 m<br></html>", layer.getToolTipText());
     85        assertEquals(layer.getColor(), Color.RED);
     86        assertEquals("<html>1 track (0 segments), 0 routes, 0 waypoints<br>Length: < 0.01 m<br></html>", layer.getToolTipText());
    8087
    8188        GpxLayer layer2 = new GpxLayer(new GpxData(), "bar", true);
    8289        assertEquals("bar", layer2.getName());
    8390        assertTrue(layer2.isLocalFile());
    84         assertEquals(Color.MAGENTA, layer2.getColorProperty().get());
     91        assertNull(layer2.getColor());
    8592        assertEquals("<html>0 tracks (0 segments), 0 routes, 0 waypoints<br>Length: < 0.01 m<br></html>", layer2.getToolTipText());
    8693
    8794        assertTrue(layer.checkSaveConditions());
  • test/unit/org/openstreetmap/josm/gui/layer/LayerTest.java

     
    77import static org.junit.Assert.assertNull;
    88import static org.junit.Assert.assertTrue;
    99
    10 import java.awt.Color;
    1110import java.io.File;
    1211
    1312import org.junit.Before;
    1413import org.junit.Rule;
    1514import org.junit.Test;
    16 import org.openstreetmap.josm.data.preferences.AbstractProperty;
    17 import org.openstreetmap.josm.data.preferences.NamedColorProperty;
    1815import org.openstreetmap.josm.data.projection.ProjectionRegistry;
    1916import org.openstreetmap.josm.testutils.JOSMTestRules;
    2017
     
    4340    }
    4441
    4542    /**
    46      * Test {@link Layer#getColorProperty()}
    47      */
    48     @Test
    49     public void testGetColorProperty() {
    50         assertEquals(null, testLayer.getColorProperty());
    51 
    52         AbstractProperty<Color> color = new LayerManagerTest.TestLayer() {
    53             @Override
    54             protected NamedColorProperty getBaseColorProperty() {
    55                 return new NamedColorProperty("x", Color.BLACK);
    56             }
    57         }.getColorProperty();
    58 
    59         assertEquals(Color.BLACK, color.get());
    60         assertEquals(Color.BLACK, color.getDefaultValue());
    61         assertEquals("clr.layer.Test Layer.x", color.getKey());
    62     }
    63 
    64     /**
    6543     * Test of {@link Layer#isInfoResizable}
    6644     */
    6745    @Test
     
    9775        testLayer.setName("Test Layer2");
    9876        assertEquals("Test Layer2", testLayer.getName());
    9977
    100         testLayer = new LayerManagerTest.TestLayer() {
    101             @Override
    102             public AbstractProperty<Color> getColorProperty() {
    103                 return new NamedColorProperty("test", Color.RED);
    104             }
    105         };
     78        testLayer = new LayerManagerTest.TestLayer();
    10679
    10780        testLayer.setName("Test Layer2");
    10881        testLayer.setName(null);
  • test/unit/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarkerTest.java

     
    4545        assertEquals("2", marker.getText());
    4646        WayPoint wpt = marker.convertToWayPoint();
    4747        assertEquals(LatLon.ZERO, wpt.getCoor());
    48         Extensions ext = (Extensions) wpt.get(GpxConstants.META_EXTENSIONS);
     48        Extensions ext = (Extensions) wpt.get(GpxConstants.EXTENSIONS_JOSM);
    4949        assertEquals("2.0", ext.get("offset"));
    5050    }
    5151}
  • test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java

     
    33
    44import static org.junit.Assert.assertEquals;
    55import static org.junit.Assert.assertNotNull;
     6import static org.junit.Assert.assertNull;
    67import static org.junit.Assert.assertTrue;
    78
    8 import java.awt.Color;
    99import java.util.Arrays;
    1010
    1111import org.junit.Before;
     
    1616import org.openstreetmap.josm.data.gpx.GpxData;
    1717import org.openstreetmap.josm.data.gpx.GpxLink;
    1818import org.openstreetmap.josm.data.gpx.WayPoint;
     19import org.openstreetmap.josm.data.osm.DataSet;
    1920import org.openstreetmap.josm.gui.MainApplication;
     21import org.openstreetmap.josm.gui.MapFrame;
     22import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2023import org.openstreetmap.josm.spi.preferences.Config;
    2124import org.openstreetmap.josm.testutils.JOSMTestRules;
    2225
     
    2831public class MarkerLayerTest {
    2932
    3033    /**
    31      * Setup tests
     34     * For creating layers
    3235     */
    3336    @Rule
    3437    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     
    4750     */
    4851    @Test
    4952    public void testMarkerLayer() {
    50         assertEquals(Color.magenta, MarkerLayer.getGenericColor());
     53        //assertEquals(Color.magenta, MarkerLayer.getGenericColor());
    5154        MarkerLayer layer = new MarkerLayer(new GpxData(), "foo", null, null);
    5255        MainApplication.getLayerManager().addLayer(layer);
    5356
    5457        assertEquals("foo", layer.getName());
    55         assertEquals(Color.magenta, layer.getColorProperty().get());
     58        assertNull(layer.getColor());
    5659        assertNotNull(layer.getIcon());
    5760        assertEquals("0 markers", layer.getToolTipText());
    5861        assertEquals("<html>foo consists of 0 markers</html>", layer.getInfoComponent());
     
    6164        GpxData gpx = new GpxData();
    6265        WayPoint wpt = new WayPoint(LatLon.ZERO);
    6366        wpt.attr.put(GpxConstants.META_LINKS, Arrays.asList(new GpxLink("https://josm.openstreetmap.de")));
    64         wpt.addExtension("offset", "1.0");
     67        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "offset", "1.0");
    6568        gpx.waypoints.add(wpt);
    6669        wpt = new WayPoint(LatLon.ZERO);
    67         wpt.addExtension("offset", "NaN");
     70        wpt.addExtensionKey(GpxConstants.EXTENSIONS_JOSM, "offset", "NaN");
    6871        gpx.waypoints.add(wpt);
    6972        layer = new MarkerLayer(gpx, "bar", null, null);
    7073
    7174        assertEquals("bar", layer.getName());
    72         assertEquals(Color.magenta, layer.getColorProperty().get());
     75        assertNull(layer.getColor());
    7376        assertNotNull(layer.getIcon());
    7477        assertEquals("3 markers", layer.getToolTipText());
    7578        assertEquals("<html>bar consists of 3 markers</html>", layer.getInfoComponent());
    7679        assertTrue(layer.getMenuEntries().length > 10);
    7780    }
     81
     82    /**
     83     * Unit test of {@code Main.map.mapView.playHeadMarker}.
     84     */
     85    @Test
     86    public void testPlayHeadMarker() {
     87        try {
     88            MainApplication.getLayerManager().addLayer(new OsmDataLayer(new DataSet(), "", null));
     89            MapFrame map = MainApplication.getMap();
     90            MarkerLayer layer = new MarkerLayer(new GpxData(), null, null, null);
     91            assertNull(map.mapView.playHeadMarker);
     92            MainApplication.getLayerManager().addLayer(layer);
     93            assertNotNull(map.mapView.playHeadMarker);
     94            MainApplication.getLayerManager().removeLayer(layer);
     95        } finally {
     96            if (MainApplication.isDisplayingMapView()) {
     97                MainApplication.getMap().mapView.playHeadMarker = null;
     98            }
     99        }
     100    }
    78101}
  • test/unit/org/openstreetmap/josm/io/GpxWriterTest.java

     
    8787                "    <vdop>0.9</vdop>%n" +
    8888                "    <pdop>1.2</pdop>%n");
    8989    }
     90
     91    /**
     92     * Tests if extensions are handled correctly when reading and writing.
     93     * Source file contains 4 tracks
     94     *  - 1x gpxx colors (garmin)
     95     *  - 1x gpxd colors
     96     *  - 2x no colors
     97     * one of the tracks without colors is assigned a new color
     98     * Then the layer is exported twice
     99     *  - once using gpxx extensions and
     100     *  - once using gpxd extensions
     101     */
     102    @Test
     103    public void testExtensions() {
     104        //TODO
     105    }
     106
    90107}