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