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