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