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        if (getDepartureTimeHourMinutes().equals(NONE)) {
448            return NONE;
449        }
450        String sDay = "";
451        if (!getDepartureTimeDay().equals("0")) {
452            sDay = getDepartureTimeDay() + ":";
453        }
454        if (!Setup.is12hrFormatEnabled()) {
455            return sDay + getDepartureTimeHourMinutes();
456        }
457        String AM_PM = TrainCommon.SPACE + Bundle.getMessage("AM");
458        int hour = Integer.parseInt(getDepartureTimeHour());
459        if (hour >= 12) {
460            AM_PM = TrainCommon.SPACE + Bundle.getMessage("PM");
461            hour = hour - 12;
462        }
463        if (hour == 0) {
464            hour = 12;
465        }
466        String shour = Integer.toString(hour);
467        return sDay + shour + ":" + getDepartureTimeMinute() + AM_PM;
468    }
469
470    @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY", justification = "firing property change doesn't matter")
471    public void setGrade(double grade) {
472        double old = _grade;
473        _grade = grade;
474        if (old != grade) {
475            setDirtyAndFirePropertyChange("grade", Double.toString(old), Double.toString(grade)); // NOI18N
476        }
477    }
478
479    public double getGrade() {
480        return _grade;
481    }
482
483    public void setTrainIconX(int x) {
484        int old = _trainIconX;
485        _trainIconX = x;
486        if (old != x) {
487            setDirtyAndFirePropertyChange("trainIconX", Integer.toString(old), Integer.toString(x)); // NOI18N
488        }
489    }
490
491    public int getTrainIconX() {
492        return _trainIconX;
493    }
494
495    public void setTrainIconY(int y) {
496        int old = _trainIconY;
497        _trainIconY = y;
498        if (old != y) {
499            setDirtyAndFirePropertyChange("trainIconY", Integer.toString(old), Integer.toString(y)); // NOI18N
500        }
501    }
502
503    public int getTrainIconY() {
504        return _trainIconY;
505    }
506
507    /**
508     * Gets the X range for detecting the manual movement of a train icon.
509     * 
510     * @return the range for detection
511     */
512    public int getTrainIconRangeX() {
513        return getLocation().getTrainIconRangeX();
514    }
515
516    /**
517     * Gets the Y range for detecting the manual movement of a train icon.
518     * 
519     * @return the range for detection
520     */
521    public int getTrainIconRangeY() {
522        return getLocation().getTrainIconRangeY();
523    }
524
525    /**
526     * Set the train icon panel coordinates to the location defaults.
527     * Coordinates are dependent on the train's departure direction.
528     */
529    public void setTrainIconCoordinates() {
530        Location l = InstanceManager.getDefault(LocationManager.class).getLocationByName(getName());
531        if ((getTrainDirection() & Location.EAST) == Location.EAST) {
532            setTrainIconX(l.getTrainIconEast().x);
533            setTrainIconY(l.getTrainIconEast().y);
534        }
535        if ((getTrainDirection() & Location.WEST) == Location.WEST) {
536            setTrainIconX(l.getTrainIconWest().x);
537            setTrainIconY(l.getTrainIconWest().y);
538        }
539        if ((getTrainDirection() & Location.NORTH) == Location.NORTH) {
540            setTrainIconX(l.getTrainIconNorth().x);
541            setTrainIconY(l.getTrainIconNorth().y);
542        }
543        if ((getTrainDirection() & Location.SOUTH) == Location.SOUTH) {
544            setTrainIconX(l.getTrainIconSouth().x);
545            setTrainIconY(l.getTrainIconSouth().y);
546        }
547    }
548
549    public Point getTrainIconCoordinates() {
550        return new Point(getTrainIconX(), getTrainIconY());
551    }
552
553    public void dispose() {
554        if (_location != null) {
555            _location.removePropertyChangeListener(this);
556        }
557        firePropertyChange(DISPOSE, null, DISPOSE);
558    }
559
560    /**
561     * Construct this Entry from XML. This member has to remain synchronized
562     * with the detailed DTD in operations-config.xml
563     *
564     * @param e Consist XML element
565     */
566    public RouteLocation(Element e) {
567        Attribute a;
568        if ((a = e.getAttribute(Xml.ID)) != null) {
569            _id = a.getValue();
570        } else {
571            log.warn("no id attribute in route location element when reading operations");
572        }
573        if ((a = e.getAttribute(Xml.LOCATION_ID)) != null) {
574            _locationId = a.getValue();
575            _location = InstanceManager.getDefault(LocationManager.class).getLocationById(a.getValue());
576            if (_location != null) {
577                _location.addPropertyChangeListener(this);
578            }
579        } // old way of storing a route location
580        else if ((a = e.getAttribute(Xml.NAME)) != null) {
581            _location = InstanceManager.getDefault(LocationManager.class).getLocationByName(a.getValue());
582            if (_location != null) {
583                _location.addPropertyChangeListener(this);
584            }
585            // force rewrite of route file
586            InstanceManager.getDefault(RouteManagerXml.class).setDirty(true);
587        }
588        if ((a = e.getAttribute(Xml.TRAIN_DIRECTION)) != null) {
589            // early releases had text for train direction
590            if (Setup.getTrainDirectionList().contains(a.getValue())) {
591                _trainDir = Setup.getDirectionInt(a.getValue());
592                log.debug("found old train direction {} new direction {}", a.getValue(), _trainDir);
593            } else {
594                try {
595                    _trainDir = Integer.parseInt(a.getValue());
596                } catch (NumberFormatException ee) {
597                    log.error("Route location ({}) direction ({}) is unknown", getName(), a.getValue());
598                }
599            }
600        }
601        if ((a = e.getAttribute(Xml.MAX_TRAIN_LENGTH)) != null) {
602            try {
603                _maxTrainLength = Integer.parseInt(a.getValue());
604            } catch (NumberFormatException ee) {
605                log.error("Route location ({}) maximum train length ({}) isn't a valid number", getName(),
606                        a.getValue());
607            }
608        }
609        if ((a = e.getAttribute(Xml.GRADE)) != null) {
610            try {
611                _grade = Double.parseDouble(a.getValue());
612            } catch (NumberFormatException ee) {
613                log.error("Route location ({}) grade ({}) isn't a valid number", getName(), a.getValue());
614            }
615        }
616        if ((a = e.getAttribute(Xml.MAX_CAR_MOVES)) != null) {
617            try {
618                _maxCarMoves = Integer.parseInt(a.getValue());
619            } catch (NumberFormatException ee) {
620                log.error("Route location ({}) maximum car moves ({}) isn't a valid number", getName(), a.getValue());
621            }
622        }
623        if ((a = e.getAttribute(Xml.RANDOM_CONTROL)) != null) {
624            _randomControl = a.getValue();
625        }
626        if ((a = e.getAttribute(Xml.PICKUPS)) != null) {
627            _pickups = a.getValue().equals(Xml.YES);
628        }
629        if ((a = e.getAttribute(Xml.DROPS)) != null) {
630            _drops = a.getValue().equals(Xml.YES);
631        }
632        if ((a = e.getAttribute(Xml.LOCAL_MOVES)) != null) {
633            _localMoves = a.getValue().equals(Xml.YES);
634        } else {
635            if (!isPickUpAllowed() || !isDropAllowed()) {
636                _localMoves = false; // disable local moves
637            }
638        }
639        if ((a = e.getAttribute(Xml.WAIT)) != null) {
640            try {
641                _wait = Integer.parseInt(a.getValue());
642            } catch (NumberFormatException ee) {
643                log.error("Route location ({}) wait ({}) isn't a valid number", getName(), a.getValue());
644            }
645        }
646        if ((a = e.getAttribute(Xml.DEPART_DAY)) != null) {
647            _departureDay = a.getValue();
648        }
649        if ((a = e.getAttribute(Xml.DEPART_TIME)) != null) {
650            _departureTime = a.getValue();
651        }
652        if ((a = e.getAttribute(Xml.BLOCKING_ORDER)) != null) {
653            try {
654                _blockingOrder = Integer.parseInt(a.getValue());
655            } catch (NumberFormatException ee) {
656                log.error("Route location ({}) blocking order ({}) isn't a valid number", getName(), a.getValue());
657            }
658        }
659        if ((a = e.getAttribute(Xml.TRAIN_ICON_X)) != null) {
660            try {
661                _trainIconX = Integer.parseInt(a.getValue());
662            } catch (NumberFormatException ee) {
663                log.error("Route location ({}) icon x ({}) isn't a valid number", getName(), a.getValue());
664            }
665        }
666        if ((a = e.getAttribute(Xml.TRAIN_ICON_Y)) != null) {
667            try {
668                _trainIconY = Integer.parseInt(a.getValue());
669            } catch (NumberFormatException ee) {
670                log.error("Route location ({}) icon y ({}) isn't a valid number", getName(), a.getValue());
671            }
672        }
673        if ((a = e.getAttribute(Xml.SEQUENCE_ID)) != null) {
674            try {
675                _sequenceNum = Integer.parseInt(a.getValue());
676            } catch (NumberFormatException ee) {
677                log.error("Route location ({}) sequence id isn't a valid number {}", getName(), a.getValue());
678            }
679        }
680        if ((a = e.getAttribute(Xml.COMMENT_COLOR)) != null) {
681            setCommentTextColor(a.getValue());
682        }
683
684        if ((a = e.getAttribute(Xml.COMMENT)) != null) {
685            _comment = a.getValue();
686        }
687    }
688
689    /**
690     * Create an XML element to represent this Entry. This member has to remain
691     * synchronized with the detailed DTD in operations-config.xml.
692     *
693     * @return Contents in a JDOM Element
694     */
695    public Element store() {
696        Element e = new Element(Xml.LOCATION);
697        e.setAttribute(Xml.ID, getId());
698        e.setAttribute(Xml.NAME, getName());
699        e.setAttribute(Xml.LOCATION_ID, getNameId());
700        e.setAttribute(Xml.SEQUENCE_ID, Integer.toString(getSequenceNumber()));
701        e.setAttribute(Xml.TRAIN_DIRECTION, Integer.toString(getTrainDirection()));
702        e.setAttribute(Xml.MAX_TRAIN_LENGTH, Integer.toString(getMaxTrainLength()));
703        e.setAttribute(Xml.GRADE, Double.toString(getGrade()));
704        e.setAttribute(Xml.MAX_CAR_MOVES, Integer.toString(getMaxCarMoves()));
705        e.setAttribute(Xml.RANDOM_CONTROL, getRandomControl());
706        e.setAttribute(Xml.PICKUPS, isPickUpAllowed() ? Xml.YES : Xml.NO);
707        e.setAttribute(Xml.DROPS, isDropAllowed() ? Xml.YES : Xml.NO);
708        e.setAttribute(Xml.LOCAL_MOVES, isLocalMovesAllowed() ? Xml.YES : Xml.NO);
709        e.setAttribute(Xml.WAIT, Integer.toString(getWait()));
710        e.setAttribute(Xml.DEPART_DAY, getDepartureTimeDay());
711        e.setAttribute(Xml.DEPART_TIME, getDepartureTimeHourMinutes());
712        e.setAttribute(Xml.BLOCKING_ORDER, Integer.toString(getBlockingOrder()));
713        e.setAttribute(Xml.TRAIN_ICON_X, Integer.toString(getTrainIconX()));
714        e.setAttribute(Xml.TRAIN_ICON_Y, Integer.toString(getTrainIconY()));
715        e.setAttribute(Xml.COMMENT_COLOR, getCommentTextColor());
716        e.setAttribute(Xml.COMMENT, getComment());
717
718        return e;
719    }
720
721    @Override
722    public void propertyChange(java.beans.PropertyChangeEvent e) {
723        if (Control.SHOW_PROPERTY) {
724            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
725                    .getNewValue());
726        }
727        if (e.getPropertyName().equals(Location.DISPOSE_CHANGED_PROPERTY)) {
728            if (_location != null) {
729                _location.removePropertyChangeListener(this);
730            }
731            _location = null;
732        }
733        // forward property name change
734        if (e.getPropertyName().equals(Location.NAME_CHANGED_PROPERTY)) {
735            firePropertyChange(e.getPropertyName(), e.getOldValue(), e.getNewValue());
736        }
737    }
738
739    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
740        InstanceManager.getDefault(RouteManagerXml.class).setDirty(true);
741        firePropertyChange(p, old, n);
742    }
743
744    private final static Logger log = LoggerFactory.getLogger(RouteLocation.class);
745
746}