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}