001package jmri.jmrit.display.layoutEditor.configurexml;
002
003import java.awt.Color;
004import java.awt.geom.Point2D;
005import java.text.DecimalFormat;
006import java.util.HashMap;
007import java.util.List;
008import java.util.Map;
009import jmri.jmrit.display.layoutEditor.*;
010import jmri.util.ColorUtil;
011import org.jdom2.Attribute;
012import org.jdom2.DataConversionException;
013import org.jdom2.Element;
014
015/**
016 * This module handles configuration for display.TrackSegment objects for a
017 * LayoutEditor.
018 *
019 * @author Bob Jacobsen Copyright (c) 2020
020 * @author David Duchamp Copyright (c) 2007
021 * @author George Warner Copyright (c) 2017-2019
022 */
023public class TrackSegmentViewXml extends LayoutTrackViewXml {
024
025    public TrackSegmentViewXml() {
026    }
027
028    /**
029     * Default implementation for storing the contents of a TrackSegment
030     *
031     * @param o Object to store, of type TrackSegment
032     * @return Element containing the complete info
033     */
034    @Override
035    public Element store(Object o) {
036
037        TrackSegmentView view = (TrackSegmentView) o;
038        TrackSegment trk = view.getTrackSegment();
039
040        Element element = new Element("tracksegment"); // NOI18N
041
042        // include attributes
043        element.setAttribute("ident", trk.getId());
044        if (!trk.getBlockName().isEmpty()) {
045            element.setAttribute("blockname", trk.getBlockName());
046        }
047
048        element.setAttribute("connect1name", trk.getConnect1Name());
049        element.setAttribute("type1", "" + htpMap.outputFromEnum(trk.getType1()) );
050        element.setAttribute("connect2name", trk.getConnect2Name());
051        element.setAttribute("type2", "" + htpMap.outputFromEnum(trk.getType2()) );
052
053        element.setAttribute("dashed",      (view.isDashed() ? "yes" : "no"));
054        element.setAttribute("mainline",    (trk.isMainline() ? "yes" : "no"));
055        element.setAttribute("hidden",      (view.isHidden() ? "yes" : "no"));
056
057        if (view.isArc()) {
058            element.setAttribute("arc",         "yes");
059            element.setAttribute("flip",        (view.isFlip() ? "yes" : "no"));
060            element.setAttribute("circle",      (view.isCircle() ? "yes" : "no"));
061            if (view.isCircle()) {
062                element.setAttribute("angle",   "" + (view.getAngle()));
063                element.setAttribute("hideConLines", (view.hideConstructionLines() ? "yes" : "no"));
064            }
065        }
066
067        if (view.isBezier()) {
068            element.setAttribute("bezier", "yes");
069            element.setAttribute("hideConLines", "" + (view.hideConstructionLines() ? "yes" : "no"));
070            // add control points
071            Element elementControlpoints = new Element("controlpoints");
072            for (int i = 0; i < view.getNumberOfBezierControlPoints(); i++) {
073                Element elementControlpoint = new Element("controlpoint");
074
075                elementControlpoint.setAttribute("index", "" + i);
076
077                Point2D pt = view.getBezierControlPoint(i);
078                // correctly working code copied from PositionablePointVewXml:
079                DecimalFormat twoDecFormat = new DecimalFormat("#.##");
080                elementControlpoint.setAttribute("x", ""+Float.valueOf(twoDecFormat.format(pt.getX())));
081                elementControlpoint.setAttribute("y", ""+Float.valueOf(twoDecFormat.format(pt.getY())));
082                // String.format follows the Locale of JMRI, resulting in ',' as decimal separator = invalid xml
083                elementControlpoints.addContent(elementControlpoint);
084            }
085            element.addContent(elementControlpoints);
086        }
087
088        // store decorations
089        Map<String, String> decorations = view.getDecorations();
090        if (decorations.size() > 0) {
091            Element decorationsElement = new Element("decorations");
092            for (Map.Entry<String, String> entry : decorations.entrySet()) {
093                String name = entry.getKey();
094                if (!name.equals("arrow") && !name.equals("bridge")
095                        && !name.equals("bumper") && !name.equals("tunnel")) {
096                    Element decorationElement = new Element("decoration");
097                    decorationElement.setAttribute("name", name);
098                    String value = entry.getValue();
099                    if (!value.isEmpty()) {
100                        decorationElement.setAttribute("value", value);
101                    }
102                    decorationsElement.addContent(decorationElement);
103                }
104            }
105            element.addContent(decorationsElement);
106
107            if (view.getArrowStyle() > 0) {
108                Element decorationElement = new Element("arrow");
109                decorationElement.setAttribute("style", Integer.toString(view.getArrowStyle()));
110                if (view.isArrowEndStart() && view.isArrowEndStop()) {
111                    decorationElement.setAttribute("end", "both");
112                } else if (view.isArrowEndStop()) {
113                    decorationElement.setAttribute("end", "stop");
114                } else {
115                    decorationElement.setAttribute("end", "start");
116                }
117                if (view.isArrowDirIn() && view.isArrowDirOut()) {
118                    decorationElement.setAttribute("direction", "both");
119                } else if (view.isArrowDirOut()) {
120                    decorationElement.setAttribute("direction", "out");
121                } else {
122                    decorationElement.setAttribute("direction", "in");
123                }
124                decorationElement.setAttribute("color", ColorUtil.colorToHexString(view.getArrowColor()));
125                decorationElement.setAttribute("linewidth", Integer.toString(view.getArrowLineWidth()));
126                decorationElement.setAttribute("length", Integer.toString(view.getArrowLength()));
127                decorationElement.setAttribute("gap", Integer.toString(view.getArrowGap()));
128                decorationsElement.addContent(decorationElement);
129            }
130            if (view.isBridgeSideLeft() || view.isBridgeSideRight()) {
131                Element decorationElement = new Element("bridge");
132                if (view.isBridgeSideLeft() && view.isBridgeSideRight()) {
133                    decorationElement.setAttribute("side", "both");
134                } else if (view.isBridgeSideLeft()) {
135                    decorationElement.setAttribute("side", "left");
136                } else {
137                    decorationElement.setAttribute("side", "right");
138                }
139                if (view.isBridgeHasEntry() && view.isBridgeHasExit()) {
140                    decorationElement.setAttribute("end", "both");
141                } else if (view.isBridgeHasEntry()) {
142                    decorationElement.setAttribute("end", "entry");
143                } else if (view.isBridgeHasExit()) {
144                    decorationElement.setAttribute("end", "exit");
145                }
146                decorationElement.setAttribute("color", ColorUtil.colorToHexString(view.getBridgeColor()));
147                decorationElement.setAttribute("linewidth", Integer.toString(view.getBridgeLineWidth()));
148                decorationElement.setAttribute("approachwidth", Integer.toString(view.getBridgeApproachWidth()));
149                decorationElement.setAttribute("deckwidth", Integer.toString(view.getBridgeDeckWidth()));
150                decorationsElement.addContent(decorationElement);
151            }
152            if (view.isBumperEndStart() || view.isBumperEndStop()) {
153                Element decorationElement = new Element("bumper");
154                if (view.isBumperEndStart() && view.isBumperEndStop()) {
155                    decorationElement.setAttribute("end", "both");
156                } else if (view.isBumperEndStop()) {
157                    decorationElement.setAttribute("end", "stop");
158                } else {
159                    decorationElement.setAttribute("end", "start");
160                }
161                decorationElement.setAttribute("color", ColorUtil.colorToHexString(view.getBumperColor()));
162                decorationElement.setAttribute("linewidth", Integer.toString(view.getBumperLineWidth()));
163                decorationElement.setAttribute("length", Integer.toString(view.getBumperLength()));
164                if (view.isBumperFlipped()) {
165                    decorationElement.setAttribute("flip", "true");
166                }
167                decorationsElement.addContent(decorationElement);
168            }
169
170            if (view.isTunnelSideLeft() || view.isTunnelSideRight()) {
171                Element decorationElement = new Element("tunnel");
172                if (view.isTunnelSideLeft() && view.isTunnelSideRight()) {
173                    decorationElement.setAttribute("side", "both");
174                } else if (view.isTunnelSideLeft()) {
175                    decorationElement.setAttribute("side", "left");
176                } else {
177                    decorationElement.setAttribute("side", "right");
178                }
179                if (view.isTunnelHasEntry() && view.isTunnelHasExit()) {
180                    decorationElement.setAttribute("end", "both");
181                } else if (view.isTunnelHasEntry()) {
182                    decorationElement.setAttribute("end", "entry");
183                } else if (view.isTunnelHasExit()) {
184                    decorationElement.setAttribute("end", "exit");
185                }
186                decorationElement.setAttribute("color", ColorUtil.colorToHexString(view.getTunnelColor()));
187                decorationElement.setAttribute("linewidth", Integer.toString(view.getTunnelLineWidth()));
188                decorationElement.setAttribute("entrancewidth", Integer.toString(view.getTunnelEntranceWidth()));
189                decorationElement.setAttribute("floorwidth", Integer.toString(view.getTunnelFloorWidth()));
190                decorationsElement.addContent(decorationElement);
191            }
192        }
193
194        //element.setAttribute("class", getClass().getName());
195        log.debug("storing old fixed class name for TrackSegment");
196        element.setAttribute("class", "jmri.jmrit.display.layoutEditor.configurexml.TrackSegmentXml");
197
198        storeLogixNG_Data(view, element);
199
200        return element;
201    }   // store
202
203    @Override
204    public boolean load(Element shared, Element perNode) {
205        log.error("Invalid method called");
206        return false;
207    }
208
209    /**
210     * Load, starting with the track segment element, then all all attributes
211     *
212     * @param element Top level Element to unpack.
213     * @param o       LayoutEditor as an Object
214     */
215    @Override
216    public void load(Element element, Object o) {
217        // create the objects
218        LayoutEditor p = (LayoutEditor) o;
219
220        // get attributes
221        String name = element.getAttribute("ident").getValue();
222
223        boolean dash = getAttributeBooleanValue(element, "dashed", true);
224        boolean main = getAttributeBooleanValue(element, "mainline", true);
225        boolean hide = getAttributeBooleanValue(element, "hidden", false);
226
227        String con1Name = element.getAttribute("connect1name").getValue();
228        String con2Name = element.getAttribute("connect2name").getValue();
229
230
231        HitPointType type1 = htpMap.inputFromAttribute(element.getAttribute("type1"));
232
233        HitPointType type2 = htpMap.inputFromAttribute(element.getAttribute("type2"));
234
235        // create the new TrackSegment and view
236        TrackSegment lt = new TrackSegment(name,
237                con1Name, type1, con2Name, type2,
238                main, p);
239        TrackSegmentView lv = new TrackSegmentView(lt, p);
240        lv.setHidden(hide);
241
242        lv.setDashed(dash);
243        lv.setArc( getAttributeBooleanValue(element, "arc", false) );
244
245        if (lv.isArc()) {
246            lv.setFlip( getAttributeBooleanValue(element, "flip", false) );
247            lv.setCircle( getAttributeBooleanValue(element, "circle", false) );
248            if (lv.isCircle()) {
249                lv.setAngle( getAttributeDoubleValue(element, "angle", 0.0) );
250            }
251        }
252
253        if ( getAttributeBooleanValue(element, "bezier", false)) {
254            // load control points
255            Element controlpointsElement = element.getChild("controlpoints");
256            if (controlpointsElement != null) {
257                List<Element> elementList = controlpointsElement.getChildren("controlpoint");
258                if (elementList != null) {
259                    if (elementList.size() >= 2) {
260                        for (Element value : elementList) {
261                            double x = 0.0;
262                            double y = 0.0;
263                            int index = 0;
264                            try {
265                                index = (value.getAttribute("index")).getIntValue();
266                                x = (value.getAttribute("x")).getFloatValue();
267                                y = (value.getAttribute("y")).getFloatValue();
268                            } catch (DataConversionException e) {
269                                log.error("failed to convert controlpoint coordinates or index attributes");
270                            }
271                            lv.setBezierControlPoint(new Point2D.Double(x, y), index);
272                        }
273                    } else {
274                        log.error("Track segment Bezier two controlpoint elements not found. (found {})", elementList.size());
275                    }
276                } else {
277                    log.error("Track segment Bezier controlpoint elements not found.");
278                }
279            } else {
280                log.error("Track segment Bezier controlpoints element not found.");
281            }
282            // NOTE: do this LAST (so reCenter won't be called yet)
283            lv.setBezier(true);
284        }
285
286        if ( getAttributeBooleanValue(element, "hideConLines", false) ) {
287            lv.hideConstructionLines(TrackSegmentView.HIDECON);
288        }
289        // load decorations
290        Element decorationsElement = element.getChild("decorations");
291        if (decorationsElement != null) {
292            List<Element> decorationElementList = decorationsElement.getChildren();
293            if (decorationElementList != null) {
294                Map<String, String> decorations = new HashMap<>();
295                for (Element decorationElement : decorationElementList) {
296                    String decorationName = decorationElement.getName();
297                    if (decorationName.equals("arrow")) {
298                        Attribute a = decorationElement.getAttribute("style");
299                        if (a != null) {
300                            try {
301                                lv.setArrowStyle(a.getIntValue());
302                            } catch (DataConversionException e) {
303                            }
304                        }
305                        // assume both ends
306                        lv.setArrowEndStart(true);
307                        lv.setArrowEndStop(true);
308                        a = decorationElement.getAttribute("end");
309                        if (a != null) {
310                            String value = a.getValue();
311                            if (value.equals("start")) {
312                                lv.setArrowEndStop(false);
313                            } else if (value.equals("stop")) {
314                                lv.setArrowEndStart(false);
315                            }
316                        }
317                        // assume both directions
318                        lv.setArrowDirIn(true);
319                        lv.setArrowDirOut(true);
320                        a = decorationElement.getAttribute("direction");
321                        if (a != null) {
322                            String value = a.getValue();
323                            if (value.equals("in")) {
324                                lv.setArrowDirOut(false);
325                            } else if (value.equals("out")) {
326                                lv.setArrowDirIn(false);
327                            }
328                        }
329                        a = decorationElement.getAttribute("color");
330                        if (a != null) {
331                            try {
332                                lv.setArrowColor(ColorUtil.stringToColor(a.getValue()));
333                            } catch (IllegalArgumentException e) {
334                                lv.setArrowColor(Color.BLACK);
335                                log.error("Invalid color {}; using black", a.getValue());
336                            }
337                        }
338                        a = decorationElement.getAttribute("linewidth");
339                        if (a != null) {
340                            try {
341                                lv.setArrowLineWidth(a.getIntValue());
342                            } catch (DataConversionException e) {
343                            }
344                        }
345                        a = decorationElement.getAttribute("length");
346                        if (a != null) {
347                            try {
348                                lv.setArrowLength(a.getIntValue());
349                            } catch (DataConversionException e) {
350                            }
351                        }
352                        a = decorationElement.getAttribute("gap");
353                        if (a != null) {
354                            try {
355                                lv.setArrowGap(a.getIntValue());
356                            } catch (DataConversionException e) {
357                            }
358                        }
359                    } else if (decorationName.equals("bridge")) {
360                        // assume both sides
361                        lv.setBridgeSideLeft(true);
362                        lv.setBridgeSideRight(true);
363                        Attribute a = decorationElement.getAttribute("side");
364                        if (a != null) {
365                            String value = a.getValue();
366                            if (value.equals("right")) {
367                                lv.setBridgeSideLeft(false);
368                            } else if (value.equals("left")) {
369                                lv.setBridgeSideRight(false);
370                            }
371                        }
372                        // assume neither end
373                        lv.setBridgeHasEntry(false);
374                        lv.setBridgeHasExit(false);
375                        a = decorationElement.getAttribute("end");
376                        if (a != null) {
377                            String value = a.getValue();
378                            if (value.equals("both")) {
379                                lv.setBridgeHasEntry(true);
380                                lv.setBridgeHasExit(true);
381                            } else if (value.equals("entry")) {
382                                lv.setBridgeHasEntry(true);
383                                lv.setBridgeHasExit(false);
384                            } else if (value.equals("exit")) {
385                                lv.setBridgeHasEntry(false);
386                                lv.setBridgeHasExit(true);
387                            }
388                        }
389
390                        a = decorationElement.getAttribute("color");
391                        if (a != null) {
392                            try {
393                                lv.setBridgeColor(ColorUtil.stringToColor(a.getValue()));
394                            } catch (IllegalArgumentException e) {
395                                lv.setBridgeColor(Color.BLACK);
396                                log.error("Invalid color {}; using black", a.getValue());
397                            }
398                        }
399
400                        a = decorationElement.getAttribute("linewidth");
401                        if (a != null) {
402                            try {
403                                lv.setBridgeLineWidth(a.getIntValue());
404                            } catch (DataConversionException e) {
405                            }
406                        }
407
408                        a = decorationElement.getAttribute("approachwidth");
409                        if (a != null) {
410                            try {
411                                lv.setBridgeApproachWidth(a.getIntValue());
412                            } catch (DataConversionException e) {
413                            }
414                        }
415
416                        a = decorationElement.getAttribute("deckwidth");
417                        if (a != null) {
418                            try {
419                                lv.setBridgeDeckWidth(a.getIntValue());
420                            } catch (DataConversionException e) {
421                            }
422                        }
423                    } else if (decorationName.equals("bumper")) {
424                        // assume both ends
425                        lv.setBumperEndStart(true);
426                        lv.setBumperEndStop(true);
427                        Attribute a = decorationElement.getAttribute("end");
428                        if (a != null) {
429                            String value = a.getValue();
430                            if (value.equals("start")) {
431                                lv.setBumperEndStop(false);
432                            } else if (value.equals("stop")) {
433                                lv.setBumperEndStart(false);
434                            }
435                        }
436
437                        a = decorationElement.getAttribute("color");
438                        if (a != null) {
439                            try {
440                                lv.setBumperColor(ColorUtil.stringToColor(a.getValue()));
441                            } catch (IllegalArgumentException e) {
442                                lv.setBumperColor(Color.BLACK);
443                                log.error("Invalid color {}; using black", a.getValue());
444                            }
445                        }
446
447                        a = decorationElement.getAttribute("linewidth");
448                        if (a != null) {
449                            try {
450                                lv.setBumperLineWidth(a.getIntValue());
451                            } catch (DataConversionException e) {
452                            }
453                        }
454
455                        a = decorationElement.getAttribute("length");
456                        if (a != null) {
457                            try {
458                                lv.setBumperLength(a.getIntValue());
459                            } catch (DataConversionException e) {
460                            }
461                        }
462
463                        a = decorationElement.getAttribute("flip");
464                        if (a != null) {
465                            try {
466                                lv.setBumperFlipped(a.getBooleanValue());
467                            } catch (DataConversionException e) {
468                            }
469                        }
470                    } else if (decorationName.equals("tunnel")) {
471                        // assume both sides
472                        lv.setTunnelSideLeft(true);
473                        lv.setTunnelSideRight(true);
474                        Attribute a = decorationElement.getAttribute("side");
475                        if (a != null) {
476                            String value = a.getValue();
477                            if (value.equals("right")) {
478                                lv.setTunnelSideLeft(false);
479                            } else if (value.equals("left")) {
480                                lv.setTunnelSideRight(false);
481                            }
482                        }
483                        // assume neither end
484                        lv.setTunnelHasEntry(false);
485                        lv.setTunnelHasExit(false);
486                        a = decorationElement.getAttribute("end");
487                        if (a != null) {
488                            String value = a.getValue();
489                            if (value.equals("both")) {
490                                lv.setTunnelHasEntry(true);
491                                lv.setTunnelHasExit(true);
492                            } else if (value.equals("entry")) {
493                                lv.setTunnelHasEntry(true);
494                                lv.setTunnelHasExit(false);
495                            } else if (value.equals("exit")) {
496                                lv.setTunnelHasEntry(false);
497                                lv.setTunnelHasExit(true);
498                            }
499                        }
500
501                        a = decorationElement.getAttribute("color");
502                        if (a != null) {
503                            try {
504                                lv.setTunnelColor(ColorUtil.stringToColor(a.getValue()));
505                            } catch (IllegalArgumentException e) {
506                                lv.setTunnelColor(Color.BLACK);
507                                log.error("Invalid color {}; using black", a.getValue());
508                            }
509                        }
510
511                        a = decorationElement.getAttribute("linewidth");
512                        if (a != null) {
513                            try {
514                                lv.setTunnelLineWidth(a.getIntValue());
515                            } catch (DataConversionException e) {
516                            }
517                        }
518
519                        a = decorationElement.getAttribute("entrancewidth");
520                        if (a != null) {
521                            try {
522                                lv.setTunnelEntranceWidth(a.getIntValue());
523                            } catch (DataConversionException e) {
524                            }
525                        }
526
527                        a = decorationElement.getAttribute("floorwidth");
528                        if (a != null) {
529                            try {
530                                lv.setTunnelFloorWidth(a.getIntValue());
531                            } catch (DataConversionException e) {
532                            }
533                        }
534                    } else {
535                        try {
536                            String eName = decorationElement.getAttribute("name").getValue();
537                            Attribute a = decorationElement.getAttribute("value");
538                            String eValue = (a != null) ? a.getValue() : "";
539                            decorations.put(eName, eValue);
540                        } catch (NullPointerException e) {  // considered normal if the attribute is not present
541                        }
542                    }
543                }
544                lv.setDecorations(decorations);
545            }
546        }
547
548        // get remaining attribute
549        Attribute a = element.getAttribute("blockname");
550        if (a != null) {
551            lt.tLayoutBlockName = a.getValue();
552        }
553
554        loadLogixNG_Data(lv, element);
555
556        p.addLayoutTrack(lt, lv);
557    }
558
559    static final EnumIO<HitPointType> htpMap = new EnumIoNamesNumbers<>(HitPointType.class);
560
561    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrackSegmentViewXml.class);
562}