001package jmri.jmrit.operations.routes;
002
003import java.awt.Color;
004import java.awt.Point;
005
006import org.jdom2.Attribute;
007import org.jdom2.Element;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
012import jmri.InstanceManager;
013import jmri.beans.PropertyChangeSupport;
014import jmri.jmrit.operations.locations.Location;
015import jmri.jmrit.operations.locations.LocationManager;
016import jmri.jmrit.operations.setup.Control;
017import jmri.jmrit.operations.setup.Setup;
018import jmri.jmrit.operations.trains.trainbuilder.TrainCommon;
019import jmri.util.ColorUtil;
020
021/**
022 * Represents a location in a route, a location can appear more than once in a
023 * route.
024 *
025 * @author Daniel Boudreau Copyright (C) 2008, 2013, 2026
026 */
027public class RouteLocation extends PropertyChangeSupport implements java.beans.PropertyChangeListener {
028
029    public static final String NONE = "";
030
031    protected String _id = NONE;
032    protected Location _location = null; // the location in the route
033    protected String _locationId = NONE; // the location's id
034    protected int _trainDir = (Setup.getTrainDirection() == Setup.EAST + Setup.WEST) ? EAST : NORTH; // train direction
035    protected int _maxTrainLength = Setup.getMaxTrainLength();
036    protected int _maxCarMoves = Setup.getCarMoves();
037    protected String _randomControl = DISABLED;
038    protected boolean _drops = true; // when true set outs allowed at this location
039    protected boolean _pickups = true; // when true pick ups allowed at this location
040    protected boolean _localMoves = true; // when true local moves allowed at this location
041    protected int _sequenceNum = 0; // used to determine location order in a route
042    protected double _grade = 0; // maximum grade between locations
043    protected int _wait = 0; // wait time at this location
044    protected String _departureTime = NONE; // hh:mm departure time from this location
045    protected String _departureDay = "0"; // departure day from this location
046    protected int _trainIconX = 0; // the x & y coordinates for the train icon
047    protected int _trainIconY = 0;
048    protected int _blockingOrder = 0;
049    protected String _comment = NONE;
050    protected Color _commentColor = Color.black;
051
052    protected int _carMoves = 0; // number of moves at this location
053    protected int _trainWeight = 0; // total car weight departing this location
054    protected int _trainLength = 0; // train length departing this location
055
056    public static final int EAST = 1; // train direction
057    public static final int WEST = 2;
058    public static final int NORTH = 4;
059    public static final int SOUTH = 8;
060
061    public static final String EAST_DIR = Setup.EAST_DIR; // train directions text
062    public static final String WEST_DIR = Setup.WEST_DIR;
063    public static final String NORTH_DIR = Setup.NORTH_DIR;
064    public static final String SOUTH_DIR = Setup.SOUTH_DIR;
065
066    public static final String DISPOSE = "routeLocationDispose"; // NOI18N
067    public static final String DELETED = Bundle.getMessage("locationDeleted");
068
069    public static final String DROP_CHANGED_PROPERTY = "dropChange"; // NOI18N
070    public static final String PICKUP_CHANGED_PROPERTY = "pickupChange"; // NOI18N
071    public static final String LOCAL_MOVES_CHANGED_PROPERTY = "localMovesChange"; // NOI18N
072    public static final String MAX_MOVES_CHANGED_PROPERTY = "maxMovesChange"; // NOI18N
073    public static final String TRAIN_DIRECTION_CHANGED_PROPERTY = "trainDirectionChange"; // NOI18N
074    public static final String DEPARTURE_TIME_CHANGED_PROPERTY = "routeDepartureTimeChange"; // NOI18N
075    public static final String MAX_LENGTH_CHANGED_PROPERTY = "maxLengthChange"; // NOI18N
076
077    public static final String DISABLED = "Off";
078
079    public RouteLocation(String id, Location location) {
080        log.debug("New route location ({}) id: {}", location.getName(), id);
081        _location = location;
082        _id = id;
083        // listen for name change or delete
084        location.addPropertyChangeListener(this);
085    }
086
087    // for combo boxes
088    @Override
089    public String toString() {
090        return getName();
091    }
092
093    public String getId() {
094        return _id;
095    }
096
097    public String getName() {
098        if (getLocation() != null) {
099            return getLocation().getName();
100        }
101        return DELETED;
102    }
103
104    public String getSplitName() {
105        if (getLocation() != null) {
106            return getLocation().getSplitName();
107        }
108        return DELETED;
109    }
110
111    private String getNameId() {
112        if (_location != null) {
113            return _location.getId();
114        }
115        return _locationId;
116    }
117
118    public Location getLocation() {
119        return _location;
120    }
121
122    public int getSequenceNumber() {
123        return _sequenceNum;
124    }
125
126    public void setSequenceNumber(int sequence) {
127        // property change not needed
128        _sequenceNum = sequence;
129    }
130
131    public int getBlockingOrder() {
132        return _blockingOrder;
133    }
134
135    public void setBlockingOrder(int order) {
136        _blockingOrder = order;
137    }
138
139    public void setComment(String comment) {
140        String old = _comment;
141        _comment = comment;
142        if (!old.equals(_comment)) {
143            setDirtyAndFirePropertyChange("RouteLocationComment", old, comment); // NOI18N
144        }
145    }
146
147    public String getComment() {
148        return _comment;
149    }
150
151    /**
152     * Sets the text color for the route comment
153     * 
154     * @param color The color of the text
155     */
156    public void setCommentColor(Color color) {
157        Color old = _commentColor;
158        _commentColor = color;
159        if (!old.equals(_commentColor)) {
160            setDirtyAndFirePropertyChange("RouteLocationCommentColor", old, color); // NOI18N
161        }
162    }
163
164    public Color getCommentColor() {
165        return _commentColor;
166    }
167
168    public String getCommentWithColor() {
169        return TrainCommon.formatColorString(getComment(), getCommentColor());
170    }
171
172    public void setCommentTextColor(String color) {
173        setCommentColor(ColorUtil.stringToColor(color));
174    }
175
176    public String getCommentTextColor() {
177        return ColorUtil.colorToColorName(getCommentColor());
178    }
179
180    public void setTrainDirection(int direction) {
181        int old = _trainDir;
182        _trainDir = direction;
183        if (old != direction) {
184            setDirtyAndFirePropertyChange(TRAIN_DIRECTION_CHANGED_PROPERTY, Integer.toString(old), Integer
185                    .toString(direction));
186        }
187    }
188
189    /**
190     * Gets the binary representation of the train's direction at this location
191     *
192     * @return int representing train direction EAST WEST NORTH SOUTH
193     */
194    public int getTrainDirection() {
195        return _trainDir;
196    }
197
198    /**
199     * Gets the String representation of the train's direction at this location
200     *
201     * @return String representing train direction at this location
202     */
203    public String getTrainDirectionString() {
204        return Setup.getDirectionString(getTrainDirection());
205    }
206
207    public void setMaxTrainLength(int length) {
208        int old = _maxTrainLength;
209        _maxTrainLength = length;
210        if (old != length) {
211            setDirtyAndFirePropertyChange(MAX_LENGTH_CHANGED_PROPERTY, Integer.toString(old), Integer.toString(length)); // NOI18N
212        }
213    }
214
215    public int getMaxTrainLength() {
216        return _maxTrainLength;
217    }
218
219    /**
220     * Set the train length departing this location when building a train
221     * 
222     * @param length The train's current length.
223     */
224    public void setTrainLength(int length) {
225        int old = _trainLength;
226        _trainLength = length;
227        if (old != length) {
228            firePropertyChange("trainLength", Integer.toString(old), Integer.toString(length)); // NOI18N
229        }
230    }
231
232    public int getTrainLength() {
233        return _trainLength;
234    }
235
236    /**
237     * Set the train weight departing this location when building a train
238     * 
239     * @param weight The train's current weight.
240     */
241    public void setTrainWeight(int weight) {
242        int old = _trainWeight;
243        _trainWeight = weight;
244        if (old != weight) {
245            firePropertyChange("trainWeight", Integer.toString(old), Integer.toString(weight)); // NOI18N
246        }
247    }
248
249    public int getTrainWeight() {
250        return _trainWeight;
251    }
252
253    public void setMaxCarMoves(int moves) {
254        int old = _maxCarMoves;
255        _maxCarMoves = moves;
256        if (old != moves) {
257            setDirtyAndFirePropertyChange(MAX_MOVES_CHANGED_PROPERTY, Integer.toString(old), Integer.toString(moves));
258        }
259    }
260
261    /**
262     * Get the maximum number of moves for this location
263     *
264     * @return maximum number of moves
265     */
266    public int getMaxCarMoves() {
267        return _maxCarMoves;
268    }
269
270    public void setRandomControl(String value) {
271        String old = _randomControl;
272        _randomControl = value;
273        if (!old.equals(value)) {
274            setDirtyAndFirePropertyChange("randomControl", old, value); // NOI18N
275        }
276    }
277
278    public String getRandomControl() {
279        return _randomControl;
280    }
281
282    /**
283     * When true allow car drops at this location
284     *
285     * @param drops when true drops allowed at this location
286     */
287    public void setDropAllowed(boolean drops) {
288        boolean old = _drops;
289        _drops = drops;
290        if (old != drops) {
291            setDirtyAndFirePropertyChange(DROP_CHANGED_PROPERTY, old ? "true" : "false", drops ? "true" : "false"); // NOI18N
292        }
293    }
294
295    public boolean isDropAllowed() {
296        return _drops;
297    }
298
299    /**
300     * When true allow car pick ups at this location
301     *
302     * @param pickups when true pick ups allowed at this location
303     */
304    public void setPickUpAllowed(boolean pickups) {
305        boolean old = _pickups;
306        _pickups = pickups;
307        if (old != pickups) {
308            setDirtyAndFirePropertyChange(PICKUP_CHANGED_PROPERTY, old ? "true" : "false", pickups ? "true" : "false"); // NOI18N
309        }
310    }
311
312    public boolean isPickUpAllowed() {
313        return _pickups;
314    }
315
316    /**
317     * When true allow local car moves at this location
318     *
319     * @param local when true local moves allowed at this location
320     */
321    public void setLocalMovesAllowed(boolean local) {
322        boolean old = _localMoves;
323        _localMoves = local;
324        if (old != local) {
325            setDirtyAndFirePropertyChange(LOCAL_MOVES_CHANGED_PROPERTY, old ? "true" : "false",
326                    local ? "true" : "false"); // NOI18N
327        }
328    }
329
330    public boolean isLocalMovesAllowed() {
331        return _localMoves;
332    }
333
334    /**
335     * Set the number of moves completed when building a train
336     * 
337     * @param moves An integer representing the amount of moves completed.
338     */
339    public void setCarMoves(int moves) {
340        int old = _carMoves;
341        _carMoves = moves;
342        if (old != moves) {
343            firePropertyChange("carMoves", Integer.toString(old), Integer.toString(moves)); // NOI18N
344        }
345    }
346
347    public int getCarMoves() {
348        return _carMoves;
349    }
350
351    public void setWait(int time) {
352        int old = _wait;
353        _wait = time;
354        if (old != time) {
355            setDirtyAndFirePropertyChange("waitTime", Integer.toString(old), Integer.toString(time)); // NOI18N
356        }
357    }
358
359    public int getWait() {
360        return _wait;
361    }
362
363    /**
364     * Sets the formated departure time from this location
365     * 
366     * @param time format hours:minutes
367     */
368    public void setDepartureTimeHourMinutes(String time) {
369        String old = _departureTime;
370        _departureTime = time;
371        if (!old.equals(time)) {
372            setDirtyAndFirePropertyChange(DEPARTURE_TIME_CHANGED_PROPERTY, old, time);
373        }
374    }
375
376    public String getDepartureTimeHourMinutes() {
377        return _departureTime;
378    }
379
380    /**
381     * Sets the departure time from this route location
382     * 
383     * @param time days:hours:minutes or hours:minutes or NONE
384     */
385    public void setDepartureTime(String time) {
386        String[] t = time.split(":");
387        if (t.length > 2) {
388            setDepartureTime(t[0], t[1], t[2]);
389        } else if (t.length > 1) {
390            setDepartureTime("0", t[0], t[1]);
391        } else {
392            setDepartureTimeHourMinutes(NONE);
393        }
394    }
395
396    public void setDepartureTime(String day, String hour, String minute) {
397        setDepartureTimeDay(day);
398        hour = String.format("%02d", Integer.parseInt(hour));
399        minute = String.format("%02d", Integer.parseInt(minute));
400        String time = hour + ":" + minute;
401        setDepartureTimeHourMinutes(time);
402    }
403
404    /**
405     * @return departure time day:hour:minutes
406     */
407    public String getDepartureTime() {
408        return getDepartureTimeDay() + ":" + getDepartureTimeHourMinutes();
409    }
410
411    /**
412     * Sets the departure day from this route location
413     * 
414     * @param day the day where "0" is today, and "1, 2 .." are the following
415     *            days.
416     */
417    public void setDepartureTimeDay(String day) {
418        String old = _departureDay;
419        if (!old.equals(day)) {
420            setDirtyAndFirePropertyChange(DEPARTURE_TIME_CHANGED_PROPERTY, old, day);
421        }
422        _departureDay = day;
423    }
424
425    public String getDepartureTimeDay() {
426        return _departureDay;
427    }
428
429    public String getDepartureTimeHour() {
430        String[] time = getDepartureTime().split(":");
431        return time[1];
432    }
433
434    public String getDepartureTimeMinute() {
435        String[] time = getDepartureTime().split(":");
436        return time[2];
437    }
438
439    /**
440     * Gets the formated departure time from this route location. Provides the
441     * day at the start if day is greater than zero. Format days:hours:minutes
442     * or hours:minutes if day = 0. Optional AM or PM 12 hour format.
443     * 
444     * @return days:hours:minutes or hours:minutes. Optional AM/PM
445     */
446    public String getFormatedDepartureTime() {
447        String sDay = "";
448        if (!getDepartureTimeDay().equals("0")) {
449            sDay = getDepartureTimeDay() + ":";
450        }
451        if (getDepartureTimeHourMinutes().equals(NONE) || !Setup.is12hrFormatEnabled()) {
452            return sDay + getDepartureTimeHourMinutes();
453        }
454        String AM_PM = TrainCommon.SPACE + Bundle.getMessage("AM");
455        int hour = Integer.parseInt(getDepartureTimeHour());
456        if (hour >= 12) {
457            AM_PM = TrainCommon.SPACE + Bundle.getMessage("PM");
458            hour = hour - 12;
459        }
460        if (hour == 0) {
461            hour = 12;
462        }
463        String shour = Integer.toString(hour);
464        return sDay + shour + ":" + getDepartureTimeMinute() + AM_PM;
465    }
466
467    @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY", justification = "firing property change doesn't matter")
468    public void setGrade(double grade) {
469        double old = _grade;
470        _grade = grade;
471        if (old != grade) {
472            setDirtyAndFirePropertyChange("grade", Double.toString(old), Double.toString(grade)); // NOI18N
473        }
474    }
475
476    public double getGrade() {
477        return _grade;
478    }
479
480    public void setTrainIconX(int x) {
481        int old = _trainIconX;
482        _trainIconX = x;
483        if (old != x) {
484            setDirtyAndFirePropertyChange("trainIconX", Integer.toString(old), Integer.toString(x)); // NOI18N
485        }
486    }
487
488    public int getTrainIconX() {
489        return _trainIconX;
490    }
491
492    public void setTrainIconY(int y) {
493        int old = _trainIconY;
494        _trainIconY = y;
495        if (old != y) {
496            setDirtyAndFirePropertyChange("trainIconY", Integer.toString(old), Integer.toString(y)); // NOI18N
497        }
498    }
499
500    public int getTrainIconY() {
501        return _trainIconY;
502    }
503
504    /**
505     * Gets the X range for detecting the manual movement of a train icon.
506     * 
507     * @return the range for detection
508     */
509    public int getTrainIconRangeX() {
510        return getLocation().getTrainIconRangeX();
511    }
512
513    /**
514     * Gets the Y range for detecting the manual movement of a train icon.
515     * 
516     * @return the range for detection
517     */
518    public int getTrainIconRangeY() {
519        return getLocation().getTrainIconRangeY();
520    }
521
522    /**
523     * Set the train icon panel coordinates to the location defaults.
524     * Coordinates are dependent on the train's departure direction.
525     */
526    public void setTrainIconCoordinates() {
527        Location l = InstanceManager.getDefault(LocationManager.class).getLocationByName(getName());
528        if ((getTrainDirection() & Location.EAST) == Location.EAST) {
529            setTrainIconX(l.getTrainIconEast().x);
530            setTrainIconY(l.getTrainIconEast().y);
531        }
532        if ((getTrainDirection() & Location.WEST) == Location.WEST) {
533            setTrainIconX(l.getTrainIconWest().x);
534            setTrainIconY(l.getTrainIconWest().y);
535        }
536        if ((getTrainDirection() & Location.NORTH) == Location.NORTH) {
537            setTrainIconX(l.getTrainIconNorth().x);
538            setTrainIconY(l.getTrainIconNorth().y);
539        }
540        if ((getTrainDirection() & Location.SOUTH) == Location.SOUTH) {
541            setTrainIconX(l.getTrainIconSouth().x);
542            setTrainIconY(l.getTrainIconSouth().y);
543        }
544    }
545
546    public Point getTrainIconCoordinates() {
547        return new Point(getTrainIconX(), getTrainIconY());
548    }
549
550    public void dispose() {
551        if (_location != null) {
552            _location.removePropertyChangeListener(this);
553        }
554        firePropertyChange(DISPOSE, null, DISPOSE);
555    }
556
557    /**
558     * Construct this Entry from XML. This member has to remain synchronized
559     * with the detailed DTD in operations-config.xml
560     *
561     * @param e Consist XML element
562     */
563    public RouteLocation(Element e) {
564        Attribute a;
565        if ((a = e.getAttribute(Xml.ID)) != null) {
566            _id = a.getValue();
567        } else {
568            log.warn("no id attribute in route location element when reading operations");
569        }
570        if ((a = e.getAttribute(Xml.LOCATION_ID)) != null) {
571            _locationId = a.getValue();
572            _location = InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue());
573            if (_location != null) {
574                _location.addPropertyChangeListener(this);
575            }
576        } // old way of storing a route location
577        else if ((a = e.getAttribute(Xml.NAME)) != null) {
578            _location = InstanceManager.getDefault(LocationManager.class).getLocationByName(a.getValue());
579            if (_location != null) {
580                _location.addPropertyChangeListener(this);
581            }
582            // force rewrite of route file
583            InstanceManager.getDefault(RouteManagerXml.class).setDirty(true);
584        }
585        if ((a = e.getAttribute(Xml.TRAIN_DIRECTION)) != null) {
586            // early releases had text for train direction
587            if (Setup.getTrainDirectionList().contains(a.getValue())) {
588                _trainDir = Setup.getDirectionInt(a.getValue());
589                log.debug("found old train direction {} new direction {}", a.getValue(), _trainDir);
590            } else {
591                try {
592                    _trainDir = Integer.parseInt(a.getValue());
593                } catch (NumberFormatException ee) {
594                    log.error("Route location ({}) direction ({}) is unknown", getName(), a.getValue());
595                }
596            }
597        }
598        if ((a = e.getAttribute(Xml.MAX_TRAIN_LENGTH)) != null) {
599            try {
600                _maxTrainLength = Integer.parseInt(a.getValue());
601            } catch (NumberFormatException ee) {
602                log.error("Route location ({}) maximum train length ({}) isn't a valid number", getName(),
603                        a.getValue());
604            }
605        }
606        if ((a = e.getAttribute(Xml.GRADE)) != null) {
607            try {
608                _grade = Double.parseDouble(a.getValue());
609            } catch (NumberFormatException ee) {
610                log.error("Route location ({}) grade ({}) isn't a valid number", getName(), a.getValue());
611            }
612        }
613        if ((a = e.getAttribute(Xml.MAX_CAR_MOVES)) != null) {
614            try {
615                _maxCarMoves = Integer.parseInt(a.getValue());
616            } catch (NumberFormatException ee) {
617                log.error("Route location ({}) maximum car moves ({}) isn't a valid number", getName(), a.getValue());
618            }
619        }
620        if ((a = e.getAttribute(Xml.RANDOM_CONTROL)) != null) {
621            _randomControl = a.getValue();
622        }
623        if ((a = e.getAttribute(Xml.PICKUPS)) != null) {
624            _pickups = a.getValue().equals(Xml.YES);
625        }
626        if ((a = e.getAttribute(Xml.DROPS)) != null) {
627            _drops = a.getValue().equals(Xml.YES);
628        }
629        if ((a = e.getAttribute(Xml.LOCAL_MOVES)) != null) {
630            _localMoves = a.getValue().equals(Xml.YES);
631        } else {
632            if (!isPickUpAllowed() || !isDropAllowed()) {
633                _localMoves = false; // disable local moves
634            }
635        }
636        if ((a = e.getAttribute(Xml.WAIT)) != null) {
637            try {
638                _wait = Integer.parseInt(a.getValue());
639            } catch (NumberFormatException ee) {
640                log.error("Route location ({}) wait ({}) isn't a valid number", getName(), a.getValue());
641            }
642        }
643        if ((a = e.getAttribute(Xml.DEPART_DAY)) != null) {
644            _departureDay = a.getValue();
645        }
646        if ((a = e.getAttribute(Xml.DEPART_TIME)) != null) {
647            _departureTime = a.getValue();
648        }
649        if ((a = e.getAttribute(Xml.BLOCKING_ORDER)) != null) {
650            try {
651                _blockingOrder = Integer.parseInt(a.getValue());
652            } catch (NumberFormatException ee) {
653                log.error("Route location ({}) blocking order ({}) isn't a valid number", getName(), a.getValue());
654            }
655        }
656        if ((a = e.getAttribute(Xml.TRAIN_ICON_X)) != null) {
657            try {
658                _trainIconX = Integer.parseInt(a.getValue());
659            } catch (NumberFormatException ee) {
660                log.error("Route location ({}) icon x ({}) isn't a valid number", getName(), a.getValue());
661            }
662        }
663        if ((a = e.getAttribute(Xml.TRAIN_ICON_Y)) != null) {
664            try {
665                _trainIconY = Integer.parseInt(a.getValue());
666            } catch (NumberFormatException ee) {
667                log.error("Route location ({}) icon y ({}) isn't a valid number", getName(), a.getValue());
668            }
669        }
670        if ((a = e.getAttribute(Xml.SEQUENCE_ID)) != null) {
671            try {
672                _sequenceNum = Integer.parseInt(a.getValue());
673            } catch (NumberFormatException ee) {
674                log.error("Route location ({}) sequence id isn't a valid number {}", getName(), a.getValue());
675            }
676        }
677        if ((a = e.getAttribute(Xml.COMMENT_COLOR)) != null) {
678            setCommentTextColor(a.getValue());
679        }
680
681        if ((a = e.getAttribute(Xml.COMMENT)) != null) {
682            _comment = a.getValue();
683        }
684    }
685
686    /**
687     * Create an XML element to represent this Entry. This member has to remain
688     * synchronized with the detailed DTD in operations-config.xml.
689     *
690     * @return Contents in a JDOM Element
691     */
692    public Element store() {
693        Element e = new Element(Xml.LOCATION);
694        e.setAttribute(Xml.ID, getId());
695        e.setAttribute(Xml.NAME, getName());
696        e.setAttribute(Xml.LOCATION_ID, getNameId());
697        e.setAttribute(Xml.SEQUENCE_ID, Integer.toString(getSequenceNumber()));
698        e.setAttribute(Xml.TRAIN_DIRECTION, Integer.toString(getTrainDirection()));
699        e.setAttribute(Xml.MAX_TRAIN_LENGTH, Integer.toString(getMaxTrainLength()));
700        e.setAttribute(Xml.GRADE, Double.toString(getGrade()));
701        e.setAttribute(Xml.MAX_CAR_MOVES, Integer.toString(getMaxCarMoves()));
702        e.setAttribute(Xml.RANDOM_CONTROL, getRandomControl());
703        e.setAttribute(Xml.PICKUPS, isPickUpAllowed() ? Xml.YES : Xml.NO);
704        e.setAttribute(Xml.DROPS, isDropAllowed() ? Xml.YES : Xml.NO);
705        e.setAttribute(Xml.LOCAL_MOVES, isLocalMovesAllowed() ? Xml.YES : Xml.NO);
706        e.setAttribute(Xml.WAIT, Integer.toString(getWait()));
707        e.setAttribute(Xml.DEPART_DAY, getDepartureTimeDay());
708        e.setAttribute(Xml.DEPART_TIME, getDepartureTimeHourMinutes());
709        e.setAttribute(Xml.BLOCKING_ORDER, Integer.toString(getBlockingOrder()));
710        e.setAttribute(Xml.TRAIN_ICON_X, Integer.toString(getTrainIconX()));
711        e.setAttribute(Xml.TRAIN_ICON_Y, Integer.toString(getTrainIconY()));
712        e.setAttribute(Xml.COMMENT_COLOR, getCommentTextColor());
713        e.setAttribute(Xml.COMMENT, getComment());
714
715        return e;
716    }
717
718    @Override
719    public void propertyChange(java.beans.PropertyChangeEvent e) {
720        if (Control.SHOW_PROPERTY) {
721            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
722                    .getNewValue());
723        }
724        if (e.getPropertyName().equals(Location.DISPOSE_CHANGED_PROPERTY)) {
725            if (_location != null) {
726                _location.removePropertyChangeListener(this);
727            }
728            _location = null;
729        }
730        // forward property name change
731        if (e.getPropertyName().equals(Location.NAME_CHANGED_PROPERTY)) {
732            firePropertyChange(e.getPropertyName(), e.getOldValue(), e.getNewValue());
733        }
734    }
735
736    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
737        InstanceManager.getDefault(RouteManagerXml.class).setDirty(true);
738        firePropertyChange(p, old, n);
739    }
740
741    private final static Logger log = LoggerFactory.getLogger(RouteLocation.class);
742
743}