001package jmri.jmrit.logix; 002 003import java.awt.Color; 004import java.awt.Component; 005import java.awt.Font; 006import java.awt.event.ActionEvent; 007import java.util.ArrayList; 008import java.util.LinkedList; 009import java.util.List; 010 011import javax.annotation.CheckForNull; 012import javax.annotation.Nonnull; 013import javax.swing.AbstractListModel; 014import javax.swing.Box; 015import javax.swing.BoxLayout; 016import javax.swing.JButton; 017import javax.swing.JLabel; 018import javax.swing.JDialog; 019import javax.swing.JList; 020import javax.swing.JPanel; 021import javax.swing.ListCellRenderer; 022import javax.swing.event.ListSelectionEvent; 023import javax.swing.event.ListSelectionListener; 024 025import jmri.Block; 026import jmri.JmriException; 027import jmri.jmrit.display.LocoIcon; 028import jmri.util.swing.JmriJOptionPane; 029 030/** 031 * Track an occupied block to adjacent blocks becoming occupied. 032 * 033 * @author Pete Cressman Copyright (C) 2013 034 */ 035public class Tracker { 036 037 private static final String TRACKER_NO_CURRENT_BLOCK = "TrackerNoCurrentBlock"; 038 private final TrackerTableAction _parent; 039 private final String _trainName; 040 private ArrayList<OBlock> _headRange; // blocks reachable from head block 041 private ArrayList<OBlock> _tailRange; // blocks reachable from tail block 042 private final ArrayList<OBlock> _lostRange = new ArrayList<>(); // blocks that lost detection 043 private final LinkedList<OBlock> _occupies = new LinkedList<>(); // blocks occupied by train 044 final long _startTime; 045 String _statusMessage; 046 private final Color _markerForeground; 047 private final Color _markerBackground; 048 private final Font _markerFont; 049 private OBlock _darkBlock = null; 050 enum PathSet {NOWAY, NOTSET, PARTIAL, SET} 051 052 /** 053 * 054 * @param block the starting block to track 055 * @param name the name of the train being tracked 056 * @param marker icon if LocoIcon was dropped on a block 057 * @param tta TrackerTableAction that manages Trackers 058 */ 059 Tracker(OBlock block, String name, LocoIcon marker, TrackerTableAction tta) { 060 _trainName = name; 061 _parent = tta; 062 _markerForeground = block.getMarkerForeground(); 063 _markerBackground = block.getMarkerBackground(); 064 _markerFont = block.getMarkerFont(); 065 block.setState(block.getState() & ~OBlock.RUNNING); // jiggle-jiggle 066 addtoOccupies(block, true); 067 _startTime = System.currentTimeMillis(); 068 block._entryTime = _startTime; 069 List<OBlock> occupy = initialRange(_parent); 070 if (!occupy.isEmpty()) { 071 new ChooseStartBlock(block, occupy, Tracker.this, _parent); 072 } else { 073 _parent.addTracker(Tracker.this); 074 } 075 if (marker != null) { 076 marker.dock(); 077 } 078 } 079 080 private List<OBlock> initialRange(TrackerTableAction parent) { 081 makeRange(); 082 if (getHeadBlock().equals(getTailBlock())) { 083 return makeChoiceList(_headRange, parent); 084 } else { // make additional block the tail 085 return makeChoiceList(_tailRange, parent); 086 } 087 } 088 089 private List<OBlock> makeChoiceList(List<OBlock> range, TrackerTableAction parent) { 090 ArrayList<OBlock> occupy = new ArrayList<>(); 091 for (OBlock b : range) { 092 if (!_occupies.contains(b) && 093 ((b.getState() & Block.OCCUPIED) != 0 || (b.getState() & Block.UNDETECTED) != 0) 094 && parent.checkBlock(b)) { 095 occupy.add(b); 096 } 097 } 098 return occupy; 099 } 100 101 /* 102 * Jiggle state so Indicator icons show block value 103 */ 104 private void showBlockValue(OBlock block) { 105 block.setValue(_trainName); 106 block.setMarkerBackground(_markerBackground); 107 block.setMarkerForeground(_markerForeground); 108 block.setMarkerFont(_markerFont); 109 block.setState(block.getState() | OBlock.RUNNING); 110 } 111 112 protected String getTrainName() { 113 return _trainName; 114 } 115 116 protected final OBlock getHeadBlock() { 117 return _occupies.peekFirst(); 118 } 119 120 protected final OBlock getTailBlock() { 121 return _occupies.peekLast(); 122 } 123 124 protected String getStatus() { 125 long et = 0; 126 OBlock block = null; 127 for (OBlock b : _occupies) { 128 long t = System.currentTimeMillis() - b._entryTime; 129 if (t >= et) { 130 et = t; 131 block = b; 132 } 133 } 134 if (block == null) { 135 return Bundle.getMessage("TrackerLocationLost", _trainName); 136 } 137 et /= 1000; 138 return Bundle.getMessage("TrackerStatus", _trainName, block.getDisplayName(), et / 60, et % 60); 139 } 140 141 /** 142 * Check if there is a path set between blkA and blkB with at most 143 * one dark block between them. If there is both a path set to exit blkA 144 * and a path set to enter blkB, the path is PathSet.SET. If an exit or 145 * an entry path is set, but not both, the path is PathSet.PARTIAL. If there 146 * is neither an exit path not an entry path set, the path is PathSet.NO. 147 * When NOT PathSet.SET between blkA and blkB, then any dark blocks between 148 * blkA and blkB are examined. All are examined for the most likely path 149 * through the dark block connecting blkA and blkB. 150 * @param blkA the current Head or Tail block 151 * @param blkB a block from the headRange or tailRange, where entry is possible 152 * @param recurse true if path can be used more than once 153 * @return one of PathSet enum values representing (how much of) a path was set 154 */ 155 private PathSet hasPathBetween(@Nonnull OBlock blkA, @Nonnull OBlock blkB, boolean recurse) 156 throws JmriException { 157 // first check if there is an exit path set from blkA, to blkB 158 PathSet pathset = PathSet.NOTSET; 159 boolean hasExitA = false; 160 boolean hasEnterB = false; 161 boolean adjacentBlock = false; 162 ArrayList<OBlock> darkBlocks = new ArrayList<>(); 163 for (Portal portal : blkA.getPortals()) { 164 OBlock block = portal.getOpposingBlock(blkA); 165 if (blkB.equals(block)) { 166 adjacentBlock = true; 167 if (!getPathsSet(blkA, portal).isEmpty()) { // set paths of blkA to portal 168 hasExitA = true; 169 if (!getPathsSet(blkB, portal).isEmpty()) { // paths of blkB to portal 170 // done, path through portal is set 171 pathset = PathSet.SET; 172 break; 173 } 174 } else if (!getPathsSet(blkB, portal).isEmpty()) { 175 hasEnterB = true; 176 } 177 } else if ((block.getState() & Block.UNDETECTED) != 0) { 178 darkBlocks.add(block); 179 } 180 } 181 if (pathset != PathSet.SET) { 182 if (hasExitA || hasEnterB) { 183 pathset = PathSet.PARTIAL; 184 } 185 } 186 if (adjacentBlock || !recurse) { 187 return pathset; 188 } 189 if (darkBlocks.isEmpty()) { 190 return PathSet.NOWAY; 191 } 192 // blkA and blkB not adjacent, so look for a connecting dark block 193 PathSet darkPathSet; 194 for (OBlock block : darkBlocks) { 195 // if more than one dark block, set _darkBlock to the one with best accessing paths 196 darkPathSet = hasDarkBlockPathBetween(blkA, block, blkB); 197 if (darkPathSet == PathSet.SET) { 198 _darkBlock = block; 199 pathset = PathSet.SET; 200 break; 201 } 202 if (darkPathSet == PathSet.PARTIAL) { 203 _darkBlock = block; 204 pathset = PathSet.PARTIAL; 205 } 206 } 207 if (_darkBlock == null) { // _darkBlocks never empty at this point 208 // no good paths, nevertheless there is an intermediate dark block 209 _darkBlock = darkBlocks.get(0); 210 } 211 return pathset; 212 } 213 214 private PathSet hasDarkBlockPathBetween(OBlock blkA, OBlock block, OBlock blkB) 215 throws JmriException { 216 PathSet pathset = PathSet.NOTSET; 217 PathSet setA = hasPathBetween(blkA, block, false); 218 PathSet setB = hasPathBetween(block, blkB, false); 219 if (setA == PathSet.SET && setB == PathSet.SET) { 220 pathset = PathSet.SET; 221 } else if (setA != PathSet.NOTSET && setB != PathSet.NOTSET) { 222 pathset = PathSet.PARTIAL; 223 } 224 return pathset; 225 } 226 227 protected PathSet hasPathInto(OBlock block) throws JmriException { 228 _darkBlock = null; 229 OBlock blk = getHeadBlock(); 230 if (blk != null) { 231 PathSet pathSet = hasPathBetween(blk, block, true); 232 if (pathSet != PathSet.NOWAY) { 233 return pathSet; 234 } 235 } 236 blk = getTailBlock(); 237 if (blk == null) { 238 throw new JmriException("No tail block!"); 239 } 240 return hasPathBetween(blk, block, true); 241 } 242 243 /** 244 * Get All paths in OBlock "block" that are set to go to Portal "portal" 245 */ 246 private List<OPath> getPathsSet(OBlock block, @Nonnull Portal portal) { 247 List<OPath> paths = portal.getPathsWithinBlock(block); 248 List<OPath> setPaths = new ArrayList<>(); 249 for (OPath path : paths) { 250 if (path.checkPathSet()) { 251 setPaths.add(path); 252 } 253 } 254 return setPaths; 255 } 256 257 /** 258 * Important to keep these sets disjoint and without duplicate entries 259 * @param b block to be added 260 */ 261 private boolean areDisjoint(OBlock b) { 262 return !(_headRange.contains(b) || _occupies.contains(b) || _tailRange.contains(b)); 263 } 264 265 private void addtoHeadRange(@CheckForNull OBlock b) { 266 if (b != null) { 267 if (areDisjoint(b)) { 268 _headRange.add(b); 269 } 270 } 271 } 272 273 private void addtoTailRange(@CheckForNull OBlock b) { 274 if (b != null) { 275 if (areDisjoint(b)) { 276 _tailRange.add(b); 277 } 278 } 279 } 280 281 private void addtoOccupies(OBlock b, boolean atHead) { 282 if (!_occupies.contains(b)) { 283 if (atHead) { 284 _occupies.addFirst(b); 285 } else { 286 _occupies.addLast(b); 287 } 288 showBlockValue(b); 289 _lostRange.remove(b); 290 } 291 } 292 293 private void removeFromOccupies(OBlock b) { 294 if (b != null) { 295 _occupies.remove(b); 296 _lostRange.remove(b); 297 } 298 } 299 300 /** 301 * Build array of blocks reachable from head and tail portals 302 * @return range of reachable blocks 303 */ 304 protected List<OBlock> makeRange() { 305 _headRange = new ArrayList<>(); 306 _tailRange = new ArrayList<>(); 307 OBlock headBlock = getHeadBlock(); 308 OBlock tailBlock = getTailBlock(); 309 if (headBlock != null) { 310 for (Portal portal : headBlock.getPortals()) { 311 OBlock block = portal.getOpposingBlock(headBlock); 312 if (block != null) { 313 if ((block.getState() & Block.UNDETECTED) != 0) { 314 for (Portal p : block.getPortals()) { 315 OBlock blk = p.getOpposingBlock(block); 316 if (!blk.equals(headBlock)) { 317 addtoHeadRange(blk); 318 } 319 } 320 } else { 321 addtoHeadRange(block); 322 } 323 } 324 } 325 } 326 if (tailBlock != null && !tailBlock.equals(headBlock)) { 327 for (Portal portal : tailBlock.getPortals()) { 328 OBlock block = portal.getOpposingBlock(tailBlock); 329 if (block != null) { 330 if ((block.getState() & Block.UNDETECTED) != 0) { 331 for (Portal p : block.getPortals()) { 332 OBlock blk = p.getOpposingBlock(block); 333 if (!blk.equals(tailBlock)) { 334 addtoTailRange(blk); 335 } 336 } 337 } else { 338 addtoTailRange(block); 339 } 340 } 341 } 342 } 343 return buildRange(); 344 } 345 346 private List<OBlock> buildRange() { 347 // make new list since tracker table is holding the old list 348 ArrayList<OBlock> range = new ArrayList<>(); // total range of train 349 if (_occupies.isEmpty()) { 350 log.warn("{} does not occupy any blocks!", _trainName); 351 } 352 range.addAll(_occupies); 353 range.addAll(_headRange); 354 range.addAll(_tailRange); 355 return range; 356 } 357 358 protected List<OBlock> getBlocksOccupied() { 359 return _occupies; 360 } 361 362 protected void stop() { 363 for (OBlock b : _occupies) { 364 if ((b.getState() & Block.UNDETECTED) != 0) { 365 removeName(b); 366 } 367 } 368 } 369 370 private void removeBlock(@Nonnull OBlock block) { 371 int size = _occupies.size(); 372 int index = _occupies.indexOf(block); 373 if (index > 0 && index < size-1) { 374 // Mid range. Temporary lost of detection? Don't remove from _occupies 375 log.warn("Tracker {} lost occupancy mid train at block \"{}\"!", _trainName, block.getDisplayName()); 376 _statusMessage = Bundle.getMessage("trackerLostBlock", _trainName, block.getDisplayName()); 377 return; 378 } 379 removeFromOccupies(block); 380 // remove any adjacent dark block or mid-range lost block 381 for (Portal p : block.getPortals()) { 382 OBlock b = p.getOpposingBlock(block); 383 if ((b.getState() & (Block.UNDETECTED | Block.UNOCCUPIED)) != 0) { 384 removeFromOccupies(b); 385 removeName(b); 386 } 387 388 } 389 removeName(block); 390 } 391 392 private void removeName(OBlock block) { 393 if (_trainName.equals(block.getValue())) { 394 block.setValue(null); 395 block.setState(block.getState() & ~OBlock.RUNNING); 396 } 397 } 398 399 protected boolean move(OBlock block, int state) { 400 _statusMessage = null; 401 if ((state & Block.OCCUPIED) != 0) { 402 if (_occupies.contains(block)) { 403 if (block.getValue() == null) { // must be a regained lost block 404 block.setValue(_trainName); 405 showBlockValue(block); 406 // don't use _statusMessage, so range listeners get adjusted 407 _parent.setStatus(Bundle.getMessage("TrackerReentry", _trainName, block.getDisplayName())); 408 _lostRange.remove(block); 409 } else if (!block.getValue().equals(_trainName)) { 410 log.error("Block \"{}\" occupied by \"{}\", but block.getValue()= {}!", 411 block.getDisplayName(), _trainName, block.getValue()); 412 } 413 } else 414 _lostRange.remove(block); 415 Warrant w = block.getWarrant(); 416 if (w != null) { 417 String msg = Bundle.getMessage("AllocatedToWarrant", 418 w.getDisplayName(), block.getDisplayName(), w.getTrainName()); 419 int idx = w.getCurrentOrderIndex(); 420 // Was it the warranted train that entered the block? 421 // Can't tell who got notified first - tracker or warrant? 422 // is distance of 1 block OK? 423 if (Math.abs(w.getIndexOfBlockAfter(block, 0) - idx) < 2) { 424 _statusMessage = msg; 425 return true; 426 } // otherwise claim it for tracker 427 } 428 if (_headRange.contains(block)) { 429 if (_darkBlock != null) { 430 addtoOccupies(_darkBlock, true); 431 } 432 addtoOccupies(block, true); 433 } else if (_tailRange.contains(block)) { 434 if (_darkBlock != null) { 435 addtoOccupies(_darkBlock, false); 436 } 437 addtoOccupies(block, false); 438 } else if (!_occupies.contains(block)) { 439 log.warn("Block \"{}\" is not within range of \"{}\"!",block.getDisplayName(),_trainName ); 440 } 441 makeRange(); 442 return true; 443 } else if ((state & Block.UNOCCUPIED) != 0) { 444 removeBlock(block); 445 int size = _occupies.size(); 446 if (size == 0) { // lost tracker 447 recover(block); 448 } else { // otherwise head or tail is holding a path fixed through a portal (thrown switch should have derailed train by now) 449 makeRange(); 450 } 451 return false; 452 } 453 return true; 454 } 455 456 @Override 457 public String toString() { 458 return _trainName; 459 } 460 461 private void recover(OBlock block) { 462 // make list of possible blocks 463 ArrayList<OBlock> list = new ArrayList<>(); 464 list.addAll(_lostRange); 465 list.addAll(_headRange); 466 list.addAll(_tailRange); 467 list.add(block); 468 469 java.awt.Toolkit.getDefaultToolkit().beep(); 470 new ChooseRecoverBlock(block, list, this, _parent); 471 _statusMessage = Bundle.getMessage(TRACKER_NO_CURRENT_BLOCK, _trainName, 472 block.getDisplayName()) + "\n" + Bundle.getMessage("TrackingStopped"); 473 } 474 475 class ChooseStartBlock extends ChooseBlock { 476 477 ChooseStartBlock(OBlock b, List<OBlock> l, Tracker t, TrackerTableAction tta) { 478 super(b, l, t, tta); 479 } 480 481 @Override 482 JPanel makeBlurb() { 483 JPanel panel = new JPanel(); 484 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 485 panel.add(new JLabel(Bundle.getMessage("MultipleStartBlocks1", getHeadBlock().getDisplayName(), _trainName))); 486 panel.add(new JLabel(Bundle.getMessage("MultipleStartBlocks2"))); 487 panel.add(new JLabel(Bundle.getMessage("MultipleStartBlocks3", _trainName))); 488 panel.add(new JLabel(Bundle.getMessage("MultipleStartBlocks4",Bundle.getMessage("ButtonStart")))); 489 return panel; 490 } 491 492 @Override 493 JPanel makeButtonPanel() { 494 JPanel panel = new JPanel(); 495 JButton startButton = new JButton(Bundle.getMessage("ButtonStart")); 496 startButton.addActionListener((ActionEvent a) -> { 497 _parent.addTracker(tracker); 498 dispose(); 499 }); 500 panel.add(startButton); 501 return panel; 502 } 503 504 @Override 505 void doAction() { 506 parent.addTracker(tracker); 507 } 508 } 509 510 private class ChooseRecoverBlock extends ChooseBlock { 511 512 ChooseRecoverBlock(OBlock block, List<OBlock> list, Tracker t, TrackerTableAction tta) { 513 super(block, list, t, tta); 514 _occupies.clear(); 515 tta.removeBlockListeners(t); 516 } 517 518 @Override 519 JPanel makeBlurb() { 520 JPanel panel = new JPanel(); 521 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 522 panel.add(new JLabel(Bundle.getMessage(TRACKER_NO_CURRENT_BLOCK, _trainName, block.getDisplayName()))); 523 panel.add(new JLabel(Bundle.getMessage("PossibleLocation", _trainName))); 524 return panel; 525 } 526 527 @Override 528 JPanel makeButtonPanel() { 529 JPanel panel = new JPanel(); 530 JButton recoverButton = new JButton(Bundle.getMessage("ButtonRecover")); 531 recoverButton.addActionListener((ActionEvent a) -> { 532 if (_occupies.isEmpty()) { 533 JmriJOptionPane.showMessageDialog(this, 534 Bundle.getMessage("RecoverOrExit", _trainName, Bundle.getMessage("ButtonStop")), 535 Bundle.getMessage("WarningTitle"), JmriJOptionPane.INFORMATION_MESSAGE); 536 } else { 537 doAction(); 538 } 539 }); 540 panel.add(recoverButton); 541 542 JButton cancelButton = new JButton(Bundle.getMessage("ButtonStop")); 543 cancelButton.addActionListener((ActionEvent a) -> doStopAction()); 544 panel.add(cancelButton); 545 return panel; 546 } 547 548 @Override 549 public void valueChanged(ListSelectionEvent e) { 550 OBlock blk = _jList.getSelectedValue(); 551 if (blk != null) { 552 String msg = null; 553 if ((blk.getState() & Block.OCCUPIED) == 0) { 554 msg = Bundle.getMessage("blockUnoccupied", blk.getDisplayName()); 555 } else { 556 Tracker t = parent.findTrackerIn(blk); 557 if (t != null && !tracker.getTrainName().equals(blk.getValue())) { 558 msg = Bundle.getMessage("blockInUse", t.getTrainName(), blk.getDisplayName()); 559 } 560 } 561 if (msg != null) { 562 JmriJOptionPane.showMessageDialog(this, msg, 563 Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE); 564 _jList.removeListSelectionListener(this); 565 list.remove(blk); 566 if (list.isEmpty()) { 567 if (!_occupies.isEmpty()) { 568 doAction(); 569 dispose(); 570 } else { 571 doStopAction(); 572 } 573 } 574 _jList.setModel(new BlockListModel(list)); 575 _jList.addListSelectionListener(this); 576 } else { 577 super.valueChanged(e); 578 } 579 } 580 } 581 582 @Override 583 void doAction() { 584 parent.addBlockListeners(tracker); 585 parent.setStatus(Bundle.getMessage("restartTracker", 586 tracker.getTrainName(), tracker.getHeadBlock().getDisplayName())); 587 dispose(); 588 } 589 590 void doStopAction() { 591 parent.stopTracker(tracker, block); 592 parent.setStatus(Bundle.getMessage(TRACKER_NO_CURRENT_BLOCK, _trainName, 593 block.getDisplayName()) + "\n" + Bundle.getMessage("TrackingStopped")); 594 dispose(); 595 } 596 597 @Override 598 public void dispose () { 599 parent.updateStatus(); 600 super.dispose(); 601 } 602 } 603 604 private abstract class ChooseBlock extends JDialog implements ListSelectionListener { 605 OBlock block; 606 TrackerTableAction parent; 607 List<OBlock> list; 608 JList<OBlock> _jList; 609 Tracker tracker; 610 611 ChooseBlock(OBlock b, List<OBlock> l, Tracker t, TrackerTableAction tta) { 612 super(tta._frame); 613 setTitle(Bundle.getMessage("TrackerTitle")); 614 block = b; 615 list = l; 616 tracker = t; 617 parent = tta; 618 619 JPanel contentPanel = new JPanel(); 620 contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); 621 622 contentPanel.add(Box.createVerticalStrut(TrackerTableAction.STRUT_SIZE)); 623 JPanel p = new JPanel(); 624 p.add(makeBlurb()); 625 contentPanel.add(p); 626 627 p = new JPanel(); 628 p.add(makeListPanel()); 629 contentPanel.add(p); 630 631 contentPanel.add(Box.createVerticalStrut(TrackerTableAction.STRUT_SIZE)); 632 contentPanel.add(makeButtonPanel()); 633 setContentPane(contentPanel); 634 635 pack(); 636 setLocation(parent._frame.getLocation()); 637 setAlwaysOnTop(true); 638 setVisible(true); 639 } 640 641 abstract JPanel makeBlurb(); 642 abstract JPanel makeButtonPanel(); 643 abstract void doAction(); 644 645 protected JPanel makeListPanel() { 646 JPanel panel = new JPanel(); 647 panel.setBorder(javax.swing.BorderFactory .createLineBorder(Color.black, 2)); 648 _jList = new JList<>(); 649 _jList.setModel(new BlockListModel(list)); 650 _jList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); 651 _jList.addListSelectionListener(this); 652 _jList.setCellRenderer(new BlockCellRenderer()); 653 panel.add(_jList); 654 return panel; 655 } 656 657 @Override 658 public void valueChanged(ListSelectionEvent e) { 659 OBlock b = _jList.getSelectedValue(); 660 if (b != null) { 661 b.setState(b.getState() & ~OBlock.RUNNING); 662 addtoOccupies(b, false); // make additional block the tail 663 b._entryTime = System.currentTimeMillis(); 664 _jList.removeListSelectionListener(this); 665 List<OBlock> blockList = initialRange(parent); 666 if (blockList.isEmpty()) { 667 doAction(); 668 dispose(); 669 } 670 _jList.setModel(new BlockListModel(blockList)); 671 _jList.addListSelectionListener(this); 672 } 673 } 674 675 class BlockCellRenderer extends JLabel implements ListCellRenderer<Object> { 676 677 @Override 678 public Component getListCellRendererComponent( 679 JList<?> list, // the list 680 Object value, // value to display 681 int index, // cell index 682 boolean isSelected, // is the cell selected 683 boolean cellHasFocus) // does the cell have focus 684 { 685 String s = ((OBlock)value).getDisplayName(); 686 setText(s); 687 if (isSelected) { 688 setBackground(list.getSelectionBackground()); 689 setForeground(list.getSelectionForeground()); 690 } else { 691 setBackground(list.getBackground()); 692 setForeground(list.getForeground()); 693 } 694 setEnabled(list.isEnabled()); 695 setFont(list.getFont()); 696 setOpaque(true); 697 return this; 698 } 699 } 700 701 class BlockListModel extends AbstractListModel<OBlock> { 702 List<OBlock> blockList; 703 704 BlockListModel(List<OBlock> bl) { 705 blockList = bl; 706 } 707 708 @Override 709 public int getSize() { 710 return blockList.size(); 711 } 712 713 @Override 714 public OBlock getElementAt(int index) { 715 return blockList.get(index); 716 } 717 } 718 } 719 720 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Tracker.class); 721 722}