001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences.display;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005import static org.openstreetmap.josm.tools.I18n.trc;
006
007import java.awt.Color;
008import java.awt.Component;
009import java.awt.Dimension;
010import java.awt.GridBagLayout;
011import java.awt.event.ActionListener;
012import java.util.Enumeration;
013
014import javax.swing.AbstractButton;
015import javax.swing.BorderFactory;
016import javax.swing.Box;
017import javax.swing.ButtonGroup;
018import javax.swing.JCheckBox;
019import javax.swing.JLabel;
020import javax.swing.JOptionPane;
021import javax.swing.JPanel;
022import javax.swing.JRadioButton;
023import javax.swing.JSlider;
024
025import org.openstreetmap.josm.actions.ExpertToggleAction;
026import org.openstreetmap.josm.data.PreferencesUtils;
027import org.openstreetmap.josm.data.preferences.NamedColorProperty;
028import org.openstreetmap.josm.gui.MainApplication;
029import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper;
030import org.openstreetmap.josm.gui.layer.markerlayer.Marker;
031import org.openstreetmap.josm.gui.layer.markerlayer.Marker.TemplateEntryProperty;
032import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.ValidationListener;
033import org.openstreetmap.josm.gui.widgets.JosmComboBox;
034import org.openstreetmap.josm.gui.widgets.JosmTextField;
035import org.openstreetmap.josm.spi.preferences.Config;
036import org.openstreetmap.josm.tools.GBC;
037import org.openstreetmap.josm.tools.Logging;
038import org.openstreetmap.josm.tools.template_engine.ParseError;
039import org.openstreetmap.josm.tools.template_engine.TemplateParser;
040
041/**
042 * Panel for GPX settings.
043 */
044public class GPXSettingsPanel extends JPanel implements ValidationListener {
045
046    private static final int WAYPOINT_LABEL_CUSTOM = 6;
047    private static final String[] LABEL_PATTERN_TEMPLATE = new String[] {Marker.LABEL_PATTERN_AUTO, Marker.LABEL_PATTERN_NAME,
048        Marker.LABEL_PATTERN_DESC, "{special:everything}", "?{ '{name}' | '{desc}' | '{formattedWaypointOffset}' }", " "};
049    private static final String[] LABEL_PATTERN_DESC = new String[] {tr("Auto"), /* gpx data field name */ trc("gpx_field", "Name"),
050        /* gpx data field name */ trc("gpx_field", "Desc(ription)"), tr("Everything"), tr("Name or offset"), tr("None"), tr("Custom")};
051
052
053    private final JRadioButton drawRawGpsLinesGlobal = new JRadioButton(tr("Use global settings"));
054    private final JRadioButton drawRawGpsLinesAll = new JRadioButton(tr("All"));
055    private final JRadioButton drawRawGpsLinesLocal = new JRadioButton(tr("Local files"));
056    private final JRadioButton drawRawGpsLinesNone = new JRadioButton(tr("None"));
057    private transient ActionListener drawRawGpsLinesActionListener;
058    private final JosmTextField drawRawGpsMaxLineLength = new JosmTextField(8);
059    private final JosmTextField drawRawGpsMaxLineLengthLocal = new JosmTextField(8);
060    private final JosmTextField drawLineWidth = new JosmTextField(2);
061    private final JCheckBox forceRawGpsLines = new JCheckBox(tr("Force lines if no segments imported"));
062    private final JCheckBox largeGpsPoints = new JCheckBox(tr("Draw large GPS points"));
063    private final JCheckBox hdopCircleGpsPoints = new JCheckBox(tr("Draw a circle from HDOP value"));
064    private final JRadioButton colorTypeVelocity = new JRadioButton(tr("Velocity (red = slow, green = fast)"));
065    private final JRadioButton colorTypeDirection = new JRadioButton(tr("Direction (red = west, yellow = north, green = east, blue = south)"));
066    private final JRadioButton colorTypeDilution = new JRadioButton(tr("Dilution of Position (red = high, green = low, if available)"));
067    private final JRadioButton colorTypeQuality = new JRadioButton(tr("Quality (RTKLib only, if available)"));
068    private final JRadioButton colorTypeTime = new JRadioButton(tr("Track date"));
069    private final JRadioButton colorTypeHeatMap = new JRadioButton(tr("Heat Map (dark = few, bright = many)"));
070    private final JRadioButton colorTypeNone = new JRadioButton(tr("Single Color (can be customized for named layers)"));
071    private final JRadioButton colorTypeGlobal = new JRadioButton(tr("Use global settings"));
072    private final JosmComboBox<String> colorTypeVelocityTune = new JosmComboBox<>(new String[] {tr("Car"), tr("Bicycle"), tr("Foot")});
073    private final JosmComboBox<String> colorTypeHeatMapTune = new JosmComboBox<>(new String[] {
074        trc("Heat map", "User Normal"),
075        trc("Heat map", "User Light"),
076        trc("Heat map", "Traffic Lights"),
077        trc("Heat map", "Inferno"),
078        trc("Heat map", "Viridis"),
079        trc("Heat map", "Wood"),
080        trc("Heat map", "Heat")});
081    private final JCheckBox colorTypeHeatMapPoints = new JCheckBox(tr("Use points instead of lines for heat map"));
082    private final JSlider colorTypeHeatMapGain = new JSlider();
083    private final JSlider colorTypeHeatMapLowerLimit = new JSlider();
084    private final JCheckBox makeAutoMarkers = new JCheckBox(tr("Create markers when reading GPX"));
085    private final JCheckBox drawGpsArrows = new JCheckBox(tr("Draw Direction Arrows"));
086    private final JCheckBox drawGpsArrowsFast = new JCheckBox(tr("Fast drawing (looks uglier)"));
087    private final JosmTextField drawGpsArrowsMinDist = new JosmTextField(8);
088    private final JCheckBox colorDynamic = new JCheckBox(tr("Dynamic color range based on data limits"));
089    private final JosmComboBox<String> waypointLabel = new JosmComboBox<>(LABEL_PATTERN_DESC);
090    private final JosmTextField waypointLabelPattern = new JosmTextField();
091    private final JosmComboBox<String> audioWaypointLabel = new JosmComboBox<>(LABEL_PATTERN_DESC);
092    private final JosmTextField audioWaypointLabelPattern = new JosmTextField();
093    private final JCheckBox useGpsAntialiasing = new JCheckBox(tr("Smooth GPX graphics (antialiasing)"));
094    private final JCheckBox drawLineWithAlpha = new JCheckBox(tr("Draw with Opacity (alpha blending) "));
095
096    private String layerName;
097    private final boolean local; // flag to display LocalOnly checkbox
098    private final boolean nonlocal; // flag to display AllLines checkbox
099
100    /**
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
105     */
106    public GPXSettingsPanel(String layerName, boolean local, boolean nonlocal) {
107        super(new GridBagLayout());
108        this.local = local;
109        this.nonlocal = nonlocal;
110        this.layerName = "layer "+layerName;
111        initComponents();
112        loadPreferences();
113    }
114
115    /**
116     * Constructs a new {@code GPXSettingsPanel}.
117     */
118    public GPXSettingsPanel() {
119        super(new GridBagLayout());
120        initComponents();
121        local = false;
122        nonlocal = false;
123        loadPreferences(); // preferences -> controls
124    }
125
126    // CHECKSTYLE.OFF: ExecutableStatementCountCheck
127    private void initComponents() {
128        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
129
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));
134
135        // drawRawGpsLines
136        ButtonGroup gpsLinesGroup = new ButtonGroup();
137        if (layerName != null) {
138            gpsLinesGroup.add(drawRawGpsLinesGlobal);
139        }
140        gpsLinesGroup.add(drawRawGpsLinesNone);
141        gpsLinesGroup.add(drawRawGpsLinesLocal);
142        gpsLinesGroup.add(drawRawGpsLinesAll);
143
144        /* ensure that default is in data base */
145
146        JLabel label = new JLabel(tr("Draw lines between raw GPS points"));
147        add(label, GBC.eol().insets(20, 0, 0, 0));
148        if (layerName != null) {
149            add(drawRawGpsLinesGlobal, GBC.eol().insets(40, 0, 0, 0));
150        }
151        add(drawRawGpsLinesNone, GBC.eol().insets(40, 0, 0, 0));
152        if (layerName == null || local) {
153            add(drawRawGpsLinesLocal, GBC.eol().insets(40, 0, 0, 0));
154        }
155        if (layerName == null || nonlocal) {
156            add(drawRawGpsLinesAll, GBC.eol().insets(40, 0, 0, 0));
157        }
158        ExpertToggleAction.addVisibilitySwitcher(label);
159        ExpertToggleAction.addVisibilitySwitcher(drawRawGpsLinesGlobal);
160        ExpertToggleAction.addVisibilitySwitcher(drawRawGpsLinesNone);
161        ExpertToggleAction.addVisibilitySwitcher(drawRawGpsLinesLocal);
162        ExpertToggleAction.addVisibilitySwitcher(drawRawGpsLinesAll);
163
164        drawRawGpsLinesActionListener = e -> {
165            boolean f = drawRawGpsLinesNone.isSelected() || drawRawGpsLinesGlobal.isSelected();
166            forceRawGpsLines.setEnabled(!f);
167            drawRawGpsMaxLineLength.setEnabled(!(f || drawRawGpsLinesLocal.isSelected()));
168            drawRawGpsMaxLineLengthLocal.setEnabled(!f);
169            drawGpsArrows.setEnabled(!f);
170            drawGpsArrowsFast.setEnabled(drawGpsArrows.isSelected() && drawGpsArrows.isEnabled());
171            drawGpsArrowsMinDist.setEnabled(drawGpsArrows.isSelected() && drawGpsArrows.isEnabled());
172        };
173
174        drawRawGpsLinesGlobal.addActionListener(drawRawGpsLinesActionListener);
175        drawRawGpsLinesNone.addActionListener(drawRawGpsLinesActionListener);
176        drawRawGpsLinesLocal.addActionListener(drawRawGpsLinesActionListener);
177        drawRawGpsLinesAll.addActionListener(drawRawGpsLinesActionListener);
178
179        // drawRawGpsMaxLineLengthLocal
180        drawRawGpsMaxLineLengthLocal.setToolTipText(
181                tr("Maximum length (in meters) to draw lines for local files. Set to ''-1'' to draw all lines."));
182        label = new JLabel(tr("Maximum length for local files (meters)"));
183        add(label, GBC.std().insets(40, 0, 0, 0));
184        add(drawRawGpsMaxLineLengthLocal, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
185        ExpertToggleAction.addVisibilitySwitcher(label);
186        ExpertToggleAction.addVisibilitySwitcher(drawRawGpsMaxLineLengthLocal);
187
188        // drawRawGpsMaxLineLength
189        drawRawGpsMaxLineLength.setToolTipText(tr("Maximum length (in meters) to draw lines. Set to ''-1'' to draw all lines."));
190        label = new JLabel(tr("Maximum length (meters)"));
191        add(label, GBC.std().insets(40, 0, 0, 0));
192        add(drawRawGpsMaxLineLength, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
193        ExpertToggleAction.addVisibilitySwitcher(label);
194        ExpertToggleAction.addVisibilitySwitcher(drawRawGpsMaxLineLength);
195
196        // forceRawGpsLines
197        forceRawGpsLines.setToolTipText(tr("Force drawing of lines if the imported data contain no line information."));
198        add(forceRawGpsLines, GBC.eop().insets(40, 0, 0, 0));
199        ExpertToggleAction.addVisibilitySwitcher(forceRawGpsLines);
200
201        // drawGpsArrows
202        drawGpsArrows.addActionListener(e -> {
203            drawGpsArrowsFast.setEnabled(drawGpsArrows.isSelected() && drawGpsArrows.isEnabled());
204            drawGpsArrowsMinDist.setEnabled(drawGpsArrows.isSelected() && drawGpsArrows.isEnabled());
205        });
206        drawGpsArrows.setToolTipText(tr("Draw direction arrows for lines, connecting GPS points."));
207        add(drawGpsArrows, GBC.eop().insets(20, 0, 0, 0));
208
209        // drawGpsArrowsFast
210        drawGpsArrowsFast.setToolTipText(tr("Draw the direction arrows using table lookups instead of complex math."));
211        add(drawGpsArrowsFast, GBC.eop().insets(40, 0, 0, 0));
212        ExpertToggleAction.addVisibilitySwitcher(drawGpsArrowsFast);
213
214        // drawGpsArrowsMinDist
215        drawGpsArrowsMinDist.setToolTipText(tr("Do not draw arrows if they are not at least this distance away from the last one."));
216        add(new JLabel(tr("Minimum distance (pixels)")), GBC.std().insets(40, 0, 0, 0));
217        add(drawGpsArrowsMinDist, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
218
219        // hdopCircleGpsPoints
220        hdopCircleGpsPoints.setToolTipText(tr("Draw a circle from HDOP value"));
221        add(hdopCircleGpsPoints, GBC.eop().insets(20, 0, 0, 0));
222        ExpertToggleAction.addVisibilitySwitcher(hdopCircleGpsPoints);
223
224        // largeGpsPoints
225        largeGpsPoints.setToolTipText(tr("Draw larger dots for the GPS points."));
226        add(largeGpsPoints, GBC.eop().insets(20, 0, 0, 0));
227
228        // drawLineWidth
229        drawLineWidth.setToolTipText(tr("Width of drawn GPX line (0 for default)"));
230        add(new JLabel(tr("Drawing width of GPX lines")), GBC.std().insets(20, 0, 0, 0));
231        add(drawLineWidth, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
232
233        // antialiasing
234        useGpsAntialiasing.setToolTipText(tr("Apply antialiasing to the GPX lines resulting in a smoother appearance."));
235        add(useGpsAntialiasing, GBC.eop().insets(20, 0, 0, 0));
236        ExpertToggleAction.addVisibilitySwitcher(useGpsAntialiasing);
237
238        // alpha blending
239        drawLineWithAlpha.setToolTipText(tr("Apply dynamic alpha-blending and adjust width based on zoom level for all GPX lines."));
240        add(drawLineWithAlpha, GBC.eop().insets(20, 0, 0, 0));
241        ExpertToggleAction.addVisibilitySwitcher(drawLineWithAlpha);
242
243        // colorTracks
244        ButtonGroup colorGroup = new ButtonGroup();
245        if (layerName != null) {
246            colorGroup.add(colorTypeGlobal);
247        }
248        colorGroup.add(colorTypeNone);
249        colorGroup.add(colorTypeVelocity);
250        colorGroup.add(colorTypeDirection);
251        colorGroup.add(colorTypeDilution);
252        colorGroup.add(colorTypeQuality);
253        colorGroup.add(colorTypeTime);
254        colorGroup.add(colorTypeHeatMap);
255
256        colorTypeNone.setToolTipText(tr("All points and track segments will have the same color. Can be customized in Layer Manager."));
257        colorTypeVelocity.setToolTipText(tr("Colors points and track segments by velocity."));
258        colorTypeDirection.setToolTipText(tr("Colors points and track segments by direction."));
259        colorTypeDilution.setToolTipText(
260                tr("Colors points and track segments by dilution of position (HDOP). Your capture device needs to log that information."));
261        colorTypeQuality.setToolTipText(
262                tr("Colors points and track segments by RTKLib quality flag (Q). Your capture device needs to log that information."));
263        colorTypeTime.setToolTipText(tr("Colors points and track segments by its timestamp."));
264        colorTypeHeatMap.setToolTipText(tr("Collected points and track segments for a position and displayed as heat map."));
265
266        // color Tracks by Velocity Tune
267        colorTypeVelocityTune.setToolTipText(tr("Allows to tune the track coloring for different average speeds."));
268
269        colorTypeHeatMapTune.setToolTipText(tr("Selects the color schema for heat map."));
270        JLabel colorTypeHeatIconLabel = new JLabel();
271
272        add(Box.createVerticalGlue(), GBC.eol().insets(0, 20, 0, 0));
273
274        add(new JLabel(tr("Track and Point Coloring")), GBC.eol().insets(20, 0, 0, 0));
275        if (layerName != null) {
276            add(colorTypeGlobal, GBC.eol().insets(40, 0, 0, 0));
277        }
278        add(colorTypeNone, GBC.eol().insets(40, 0, 0, 0));
279        add(colorTypeVelocity, GBC.std().insets(40, 0, 0, 0));
280        add(colorTypeVelocityTune, GBC.eop().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
281        add(colorTypeDirection, GBC.eol().insets(40, 0, 0, 0));
282        add(colorTypeDilution, GBC.eol().insets(40, 0, 0, 0));
283        add(colorTypeQuality, GBC.eol().insets(40, 0, 0, 0));
284        add(colorTypeTime, GBC.eol().insets(40, 0, 0, 0));
285        add(colorTypeHeatMap, GBC.std().insets(40, 0, 0, 0));
286        add(colorTypeHeatIconLabel, GBC.std().insets(5, 0, 0, 5));
287        add(colorTypeHeatMapTune, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
288
289        JLabel colorTypeHeatMapGainLabel = new JLabel(tr("Overlay gain adjustment"));
290        JLabel colorTypeHeatMapLowerLimitLabel = new JLabel(tr("Lower limit of visibility"));
291        add(colorTypeHeatMapGainLabel, GBC.std().insets(80, 0, 0, 0));
292        add(colorTypeHeatMapGain, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
293        add(colorTypeHeatMapLowerLimitLabel, GBC.std().insets(80, 0, 0, 0));
294        add(colorTypeHeatMapLowerLimit, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
295        add(colorTypeHeatMapPoints, GBC.eol().insets(60, 0, 0, 0));
296
297        colorTypeHeatMapGain.setToolTipText(tr("Adjust the gain of overlay blending."));
298        colorTypeHeatMapGain.setOrientation(JSlider.HORIZONTAL);
299        colorTypeHeatMapGain.setPaintLabels(true);
300        colorTypeHeatMapGain.setMinimum(-10);
301        colorTypeHeatMapGain.setMaximum(+10);
302        colorTypeHeatMapGain.setMinorTickSpacing(1);
303        colorTypeHeatMapGain.setMajorTickSpacing(5);
304
305        colorTypeHeatMapLowerLimit.setToolTipText(tr("Draw all GPX traces that exceed this threshold."));
306        colorTypeHeatMapLowerLimit.setOrientation(JSlider.HORIZONTAL);
307        colorTypeHeatMapLowerLimit.setMinimum(0);
308        colorTypeHeatMapLowerLimit.setMaximum(254);
309        colorTypeHeatMapLowerLimit.setPaintLabels(true);
310        colorTypeHeatMapLowerLimit.setMinorTickSpacing(10);
311        colorTypeHeatMapLowerLimit.setMajorTickSpacing(100);
312
313        colorTypeHeatMapPoints.setToolTipText(tr("Render engine uses points with simulated position error instead of lines. "));
314
315        // iterate over the buttons, add change listener to any change event
316        for (Enumeration<AbstractButton> button = colorGroup.getElements(); button.hasMoreElements();) {
317            (button.nextElement()).addChangeListener(e -> {
318                colorTypeVelocityTune.setEnabled(colorTypeVelocity.isSelected());
319                colorTypeHeatMapTune.setEnabled(colorTypeHeatMap.isSelected());
320                colorTypeHeatMapPoints.setEnabled(colorTypeHeatMap.isSelected());
321                colorTypeHeatMapGain.setEnabled(colorTypeHeatMap.isSelected());
322                colorTypeHeatMapLowerLimit.setEnabled(colorTypeHeatMap.isSelected());
323                colorTypeHeatMapGainLabel.setEnabled(colorTypeHeatMap.isSelected());
324                colorTypeHeatMapLowerLimitLabel.setEnabled(colorTypeHeatMap.isSelected());
325                colorDynamic.setEnabled(colorTypeVelocity.isSelected() || colorTypeDilution.isSelected());
326            });
327        }
328
329        colorTypeHeatMapTune.addActionListener(e -> {
330            final Dimension dim = colorTypeHeatMapTune.getPreferredSize();
331            if (null != dim) {
332                // get image size of environment
333                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));
343            }
344        });
345
346        ExpertToggleAction.addVisibilitySwitcher(colorTypeDirection);
347        ExpertToggleAction.addVisibilitySwitcher(colorTypeDilution);
348        ExpertToggleAction.addVisibilitySwitcher(colorTypeQuality);
349        ExpertToggleAction.addVisibilitySwitcher(colorTypeHeatMapLowerLimit);
350        ExpertToggleAction.addVisibilitySwitcher(colorTypeHeatMapLowerLimitLabel);
351
352        colorDynamic.setToolTipText(tr("Colors points and track segments by data limits."));
353        add(colorDynamic, GBC.eop().insets(40, 0, 0, 0));
354        ExpertToggleAction.addVisibilitySwitcher(colorDynamic);
355
356        if (layerName == null) {
357            // Setting waypoints for gpx layer doesn't make sense - waypoints are shown in marker layer that has different name - so show
358            // this only for global config
359
360            // waypointLabel
361            label = new JLabel(tr("Waypoint labelling"));
362            add(label, GBC.std().insets(20, 0, 0, 0));
363            label.setLabelFor(waypointLabel);
364            add(waypointLabel, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
365            waypointLabel.addActionListener(e -> updateWaypointPattern(waypointLabel, waypointLabelPattern));
366            updateWaypointLabelCombobox(waypointLabel, waypointLabelPattern, TemplateEntryProperty.forMarker(layerName));
367            add(waypointLabelPattern, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 0, 0, 5));
368            ExpertToggleAction.addVisibilitySwitcher(label);
369            ExpertToggleAction.addVisibilitySwitcher(waypointLabel);
370            ExpertToggleAction.addVisibilitySwitcher(waypointLabelPattern);
371
372            // audioWaypointLabel
373            Component glue = Box.createVerticalGlue();
374            add(glue, GBC.eol().insets(0, 20, 0, 0));
375            ExpertToggleAction.addVisibilitySwitcher(glue);
376
377            label = new JLabel(tr("Audio waypoint labelling"));
378            add(label, GBC.std().insets(20, 0, 0, 0));
379            label.setLabelFor(audioWaypointLabel);
380            add(audioWaypointLabel, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
381            audioWaypointLabel.addActionListener(e -> updateWaypointPattern(audioWaypointLabel, audioWaypointLabelPattern));
382            updateWaypointLabelCombobox(audioWaypointLabel, audioWaypointLabelPattern, TemplateEntryProperty.forAudioMarker(layerName));
383            add(audioWaypointLabelPattern, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 0, 0, 5));
384            ExpertToggleAction.addVisibilitySwitcher(label);
385            ExpertToggleAction.addVisibilitySwitcher(audioWaypointLabel);
386            ExpertToggleAction.addVisibilitySwitcher(audioWaypointLabelPattern);
387        }
388
389        add(Box.createVerticalGlue(), GBC.eol().fill(GBC.BOTH));
390    }
391    // CHECKSTYLE.ON: ExecutableStatementCountCheck
392
393    /**
394     * Loads preferences to UI controls
395     */
396    public final void loadPreferences() {
397        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
401            drawRawGpsLinesGlobal.setSelected(true);
402        } 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            }
411        }
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));
433        useGpsAntialiasing.setSelected(Config.getPref().getBoolean("mappaint.gpx.use-antialiasing", false));
434
435        drawRawGpsLinesActionListener.actionPerformed(null);
436
437        if (layerName != null && Config.getPref().get("draw.rawgps.colors."+layerName).isEmpty()) {
438            colorTypeGlobal.setSelected(true);
439            colorDynamic.setSelected(false);
440            colorDynamic.setEnabled(false);
441            colorTypeHeatMapPoints.setSelected(false);
442            colorTypeHeatMapGain.setValue(0);
443            colorTypeHeatMapLowerLimit.setValue(0);
444        } else {
445            int colorType = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.colors", layerName, 0);
446            switch (colorType) {
447            case 0: colorTypeNone.setSelected(true); break;
448            case 1: colorTypeVelocity.setSelected(true); break;
449            case 2: colorTypeDilution.setSelected(true); break;
450            case 3: colorTypeDirection.setSelected(true); break;
451            case 4: colorTypeTime.setSelected(true); break;
452            case 5: colorTypeHeatMap.setSelected(true); break;
453            case 6: colorTypeQuality.setSelected(true); break;
454            default: Logging.warn("Unknown color type: " + colorType);
455            }
456            int ccts = PreferencesUtils.getInteger(Config.getPref(), "draw.rawgps.colorTracksTune", layerName, 45);
457            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));
468        }
469    }
470
471    /**
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
475     * @return {@code true} when restart is required, {@code false} otherwise
476     */
477    public boolean savePreferences(String layerName, boolean locLayer) {
478        String layerNameDot = ".layer "+layerName;
479        if (layerName == null) {
480            layerNameDot = "";
481        }
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);
492        } 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());
496            }
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());
507        }
508
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());
513
514        Config.getPref().putBoolean("mappaint.gpx.use-antialiasing", useGpsAntialiasing.isSelected());
515
516        TemplateEntryProperty.forMarker(layerName).put(waypointLabelPattern.getText());
517        TemplateEntryProperty.forAudioMarker(layerName).put(audioWaypointLabelPattern.getText());
518
519        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);
523            return false;
524        } else if (colorTypeVelocity.isSelected()) {
525            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 1);
526        } else if (colorTypeDilution.isSelected()) {
527            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 2);
528        } else if (colorTypeDirection.isSelected()) {
529            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 3);
530        } else if (colorTypeTime.isSelected()) {
531            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 4);
532        } else if (colorTypeHeatMap.isSelected()) {
533            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 5);
534        } else if (colorTypeQuality.isSelected()) {
535            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 6);
536        } else {
537            Config.getPref().putInt("draw.rawgps.colors"+layerNameDot, 0);
538        }
539        Config.getPref().putBoolean("draw.rawgps.colors.dynamic"+layerNameDot, colorDynamic.isSelected());
540        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());
546
547        return false;
548    }
549
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
558    private static void updateWaypointLabelCombobox(JosmComboBox<String> cb, JosmTextField tf, TemplateEntryProperty property) {
559        String labelPattern = property.getAsString();
560        boolean found = false;
561        for (int i = 0; i < LABEL_PATTERN_TEMPLATE.length; i++) {
562            if (LABEL_PATTERN_TEMPLATE[i].equals(labelPattern)) {
563                cb.setSelectedIndex(i);
564                found = true;
565                break;
566            }
567        }
568        if (!found) {
569            cb.setSelectedIndex(WAYPOINT_LABEL_CUSTOM);
570            tf.setEnabled(true);
571            tf.setText(labelPattern);
572        }
573    }
574
575    private static void updateWaypointPattern(JosmComboBox<String> cb, JosmTextField tf) {
576        if (cb.getSelectedIndex() == WAYPOINT_LABEL_CUSTOM) {
577            tf.setEnabled(true);
578        } else {
579            tf.setEnabled(false);
580            tf.setText(LABEL_PATTERN_TEMPLATE[cb.getSelectedIndex()]);
581        }
582    }
583
584    @Override
585    public boolean validatePreferences() {
586        TemplateParser parser = new TemplateParser(waypointLabelPattern.getText());
587        try {
588            parser.parse();
589        } catch (ParseError e) {
590            Logging.warn(e);
591            JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
592                    tr("Incorrect waypoint label pattern: {0}", e.getMessage()), tr("Incorrect pattern"), JOptionPane.ERROR_MESSAGE);
593            waypointLabelPattern.requestFocus();
594            return false;
595        }
596        parser = new TemplateParser(audioWaypointLabelPattern.getText());
597        try {
598            parser.parse();
599        } catch (ParseError e) {
600            Logging.warn(e);
601            JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
602                    tr("Incorrect audio waypoint label pattern: {0}", e.getMessage()), tr("Incorrect pattern"), JOptionPane.ERROR_MESSAGE);
603            audioWaypointLabelPattern.requestFocus();
604            return false;
605        }
606        return true;
607    }
608}