001package jmri.jmrit.logix;
002
003
004import java.awt.BorderLayout;
005import java.awt.Color;
006import java.awt.Dimension;
007import java.awt.FlowLayout;
008import java.awt.event.ActionEvent;
009import java.awt.event.MouseEvent;
010import java.awt.event.MouseListener;
011import java.beans.PropertyChangeListener;
012import java.util.ArrayList;
013import java.util.HashMap;
014import java.util.List;
015import java.util.Set;
016import javax.annotation.Nonnull;
017import javax.swing.AbstractAction;
018import javax.swing.AbstractListModel;
019import javax.swing.Box;
020import javax.swing.BoxLayout;
021import javax.swing.ButtonGroup;
022import javax.swing.JButton;
023import javax.swing.JDialog;
024import javax.swing.JLabel;
025import javax.swing.JList;
026import javax.swing.JMenu;
027import javax.swing.JMenuBar;
028import javax.swing.JMenuItem;
029import javax.swing.JOptionPane;
030import javax.swing.JPanel;
031import javax.swing.JRadioButtonMenuItem;
032import javax.swing.JScrollPane;
033import javax.swing.JTable;
034import javax.swing.JTextField;
035import javax.swing.event.ListSelectionEvent;
036import javax.swing.event.ListSelectionListener;
037import javax.swing.table.AbstractTableModel;
038import javax.swing.table.TableRowSorter;
039
040import jmri.Block;
041import jmri.InstanceInitializer;
042import jmri.InstanceManager;
043import jmri.JmriException;
044import jmri.implementation.AbstractInstanceInitializer;
045import jmri.jmrit.display.LocoIcon;
046import jmri.jmrit.display.palette.ItemPalette;
047import jmri.jmrit.picker.PickListModel;
048import jmri.jmrit.picker.PickPanel;
049import jmri.util.JmriJFrame;
050import jmri.util.table.ButtonEditor;
051import jmri.util.table.ButtonRenderer;
052
053import org.openide.util.lookup.ServiceProvider;
054import org.slf4j.Logger;
055import org.slf4j.LoggerFactory;
056
057/**
058 * This class displays a table of the occupancy detection trackers. It does
059 * the listening of block sensors for all the Trackers and chooses the tracker most
060 * likely to have entered a block becoming active or leaving a block when it 
061 * becomes inactive.
062 *
063 * @author Peter Cressman
064 */
065public class TrackerTableAction extends AbstractAction implements PropertyChangeListener{
066
067    static int STRUT_SIZE = 10;
068
069    private final ArrayList<Tracker> _trackerList = new ArrayList<>();
070    private final HashMap<OBlock, ArrayList<Tracker>> _trackerBlocks = new HashMap<>();
071    protected TableFrame _frame;
072    private ChooseTracker _trackerChooser;
073    private boolean _requirePaths;
074
075    private TrackerTableAction(String menuOption) {
076        super(menuOption);
077    }
078
079    @Override
080    public void actionPerformed(ActionEvent e) {
081        if (_frame != null) {
082            _frame.setVisible(true);
083        } else {
084            _frame = new TableFrame();
085        }
086    }
087
088    public synchronized boolean mouseClickedOnBlock(OBlock block) {
089        if (_frame != null) {
090            return _frame.mouseClickedOnBlock(block);
091        }
092        return false;
093    }
094
095    /**
096     * Create and register a new Tracker.
097     * @param block starting head block of the Tracker
098     * @param name name of the Tracker
099     * @param marker LocoIcon dropped on the block (optional)
100     * @return true if successfully created.
101     */
102    public boolean markNewTracker(OBlock block, String name, LocoIcon marker) {
103        if (_frame == null) {
104            _frame = new TableFrame();
105        }
106        if (name == null && marker != null) {
107            name = marker.getUnRotatedText();
108        }
109        return makeTracker(block, name, marker);
110    }
111
112    private boolean makeTracker(OBlock block, String name, LocoIcon marker) {
113        String msg = null;
114        
115        if ((block.getState() & Block.OCCUPIED) == 0) {
116            msg = Bundle.getMessage("blockUnoccupied", block.getDisplayName());
117        } else if (name == null || name.length() == 0) {
118            msg = Bundle.getMessage("noTrainName");
119        } else if (nameInuse(name)) {
120            msg = Bundle.getMessage("duplicateName", name);
121        } else {
122            Tracker t = findTrackerIn(block);
123            if (t != null && !name.equals(block.getValue())) {
124                msg = Bundle.getMessage("blockInUse", t.getTrainName(), block.getDisplayName());
125            } else {
126                Warrant w = block.getWarrant();
127                if (w != null) {
128                    msg = Bundle.getMessage("AllocatedToWarrant",
129                            w.getDisplayName(), block.getDisplayName(), w.getTrainName());   
130                }
131            }
132        }
133        if (msg != null) {
134            JOptionPane.showMessageDialog(_frame, msg,
135                    Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
136            return false;
137        }
138        block.setValue(name);
139        new Tracker(block, name, marker, this);
140        return true;
141    }
142
143    /**
144     * @deprecated since 4.17.2 - use markNewTracker instead.
145     * @param block starting head block of the Tracker
146     * @param name name of the Tracker
147     * @return the new Tracker
148     */
149    @Deprecated
150    public Tracker addTracker(OBlock block, String name) {
151        markNewTracker(block, name, null);
152        return findTrackerIn(block);
153    }
154
155    protected void addTracker(Tracker t) {
156        synchronized(this) {
157            _trackerList.add(t);
158        }
159        addBlockListeners(t);
160        if (_frame == null) {
161            _frame = new TableFrame();
162        }
163        _frame._model.fireTableDataChanged();
164        _frame.setStatus(Bundle.getMessage("startTracker",
165               t.getTrainName(), t.getHeadBlock().getDisplayName()));
166    }
167
168    protected boolean checkBlock(OBlock b) {
169        if (findTrackerIn(b) == null && b.getWarrant() == null) {
170            b.setValue(null);
171            return true;
172        }
173        return false;
174    }
175
176    boolean nameInuse(String name) {
177        for (Tracker t : _trackerList) {
178            if (name.equals(t.getTrainName())) {
179                return true;
180            }
181        }
182        return false;
183    }
184
185    /**
186     * Stop a Tracker from tracking and remove from list
187     * @param t Tracker to be stopped
188     * @param b Block Tracker of its last move. Optional, for display purpose only.
189     */
190   public void stopTracker(Tracker t, OBlock b) {
191        if (_frame == null) {
192            _frame = new TableFrame();
193        }
194        stopTrain(t, b);
195    }
196
197   protected void setStatus(String msg) {
198       _frame.setStatus(msg);
199   }
200    /**
201     * See if any Trackers are occupying a given block.
202     * @param b Block being queried
203     * @return Tracker if found
204     */
205    public Tracker findTrackerIn(OBlock b) {
206        for (Tracker t : _trackerList) {
207            if (t.getBlocksOccupied().contains(b)) {
208                return t;
209            }
210        }
211        return null;
212    }
213
214    public void updateStatus() {
215        _frame._model.fireTableDataChanged();
216
217    }
218    /**
219     * Adds listeners to all blocks in the range of a Tracker. Called when a
220     * new tracker is created.
221     * @param tracker Tracker that is about to start
222     */
223    protected void addBlockListeners(Tracker tracker) {
224        List<OBlock> range = tracker.makeRange();
225        for (OBlock oBlock : range) {
226            addBlockListener(oBlock, tracker);
227        }
228    }
229
230    /**
231     * Adds listener to a block when a tracker enters.
232     */
233    private void addBlockListener(OBlock block, Tracker tracker) {
234        ArrayList<Tracker> trackers = _trackerBlocks.get(block);
235        if (trackers == null) {
236            trackers = new ArrayList<>();
237            trackers.add(tracker);
238            if ((block.getState() & Block.UNDETECTED) == 0) {
239                _trackerBlocks.put(block, trackers);
240                block.addPropertyChangeListener(this);
241            }
242        } else {
243            if (trackers.isEmpty()) {
244                if ((block.getState() & Block.UNDETECTED) == 0) {
245                    block.addPropertyChangeListener(this);
246                }
247            }
248            if (!trackers.contains(tracker)) {
249                trackers.add(tracker);
250            }
251        }
252    }
253
254    /**
255     * Do Venn Diagram between the two sets. Keep listeners held in common.
256     * Add new listeners. Remove old.
257     */
258    private void adjustBlockListeners(List<OBlock> oldRange, List<OBlock> newRange, Tracker tracker) {
259         for (OBlock b : newRange) {
260            if (oldRange.contains(b)) {
261                oldRange.remove(b);
262                continue; // held in common. keep listener
263            }
264            addBlockListener(b, tracker);       // new block.  Add Listener
265        }
266        // blocks left in oldRange that were not found in newRange.  Remove Listeners
267        for (OBlock b :oldRange) {
268            removeBlockListener(b, tracker);
269        }
270
271    }
272
273    protected void removeBlockListeners(Tracker tracker) {
274        for (OBlock block : _trackerBlocks.keySet()) {
275            removeBlockListener(block, tracker);
276        }
277    }
278
279    private void removeBlockListener(OBlock block, Tracker tracker) {
280        List<Tracker> trackers = _trackerBlocks.get(block);
281        if (trackers != null) {
282            trackers.remove(tracker);
283            if (trackers.isEmpty()) {
284                block.removePropertyChangeListener(this);
285            }
286        }
287    }
288
289    @Override
290    public synchronized void propertyChange(java.beans.PropertyChangeEvent evt) {
291        if (evt.getPropertyName().equals("state")) {
292            OBlock block = (OBlock) evt.getSource();
293            int state = ((Number) evt.getNewValue()).intValue();
294            int oldState = ((Number) evt.getOldValue()).intValue();
295            // The "jiggle" (see tracker.showBlockValue() causes some state changes to be duplicated.
296            // The following washes out the extra notifications
297            if ((state & Block.UNOCCUPIED) == (oldState & Block.UNOCCUPIED)
298                    && (state & Block.OCCUPIED) == (oldState & Block.OCCUPIED)) {
299                return;
300            }
301            ArrayList<Tracker> trackerListeners = _trackerBlocks.get(block);
302            if (trackerListeners == null || trackerListeners.isEmpty()) {
303                log.error("No Trackers found for block \"{}\" going to state= {}", 
304                        block.getDisplayName(), state);
305                block.removePropertyChangeListener(this);
306                return;
307            }
308            if ((state & Block.OCCUPIED) != 0) {   // going occupied
309                List<Tracker> trackers = getAvailableTrackers(block);
310                if (trackers.isEmpty()) {
311                    return;
312                }
313                if (trackers.size() > 1) { // if several trackers listen for this block, user must identify which one.
314                    if (_trackerChooser != null) {
315                        _trackerChooser.dispose();
316                    }
317                    java.awt.Toolkit.getDefaultToolkit().beep();
318                    _trackerChooser = new ChooseTracker(block, trackers, state);
319                    return;
320                }
321               
322                Tracker tracker = trackers.get(0);
323                if (block.getValue() != null &&  !block.getValue().equals(tracker.getTrainName())) {
324                    log.error("Block \"{} \" going active with value= {} for Tracker {}! Who/What is \"{}\"?",
325                            block.getDisplayName(), block.getValue(), tracker.getTrainName(), block.getValue());
326                    return;
327               } else {
328                   if (!_requirePaths) {
329                       try {
330                           tracker.hasPathInto(block);
331                       } catch (JmriException je) {
332                           log.error("{} {}", tracker.getTrainName(), je.getMessage());
333                           return;
334                       }
335                   }
336                   processTrackerStateChange(tracker, block, state);
337               }
338            } else if ((state & Block.UNOCCUPIED) != 0) {
339                if (_trackerChooser != null) {
340                    _trackerChooser.checkClose(block);
341                }
342                for (Tracker t : trackerListeners) {
343                    if (t.getBlocksOccupied().contains(block)) {
344                        processTrackerStateChange(t, block, state);
345                        break;
346                    }
347                }
348            }
349        }
350        _frame._model.fireTableDataChanged();
351    }
352
353    private List<Tracker> getAvailableTrackers(OBlock block) {
354        List<Tracker> trackers = new ArrayList<>();
355        ArrayList<Tracker> trackerListeners = _trackerBlocks.get(block);
356        if (_requirePaths) {
357            ArrayList<Tracker> partials = new ArrayList<>();
358            // filter for trackers with paths set into block
359            for (Tracker t : trackerListeners) {
360                try {
361                    switch (t.hasPathInto(block)) {
362                        case SET:
363                            trackers.add(t);
364                            break;
365                        case PARTIAL:
366                            partials.add(t);
367                            break;
368                        default:
369                            break;
370                    }
371                } catch (JmriException je) {
372                    log.error("{} {}", t.getTrainName(), je.getMessage());
373                }
374            }
375            if (trackers.isEmpty()) {   // nobody has paths set.
376                // even so, likely to be possible for somebody to get there
377                if (!partials.isEmpty()) {
378                    trackers = partials; // OK, maybe not all switches are lined up
379                } else {
380                    trackers = trackerListeners; // maybe even this bad. 
381                }
382            }
383        } else {
384            trackers = trackerListeners;
385        }
386        return trackers;
387    }
388    /**
389     * Called when a state change has occurred for one the blocks listened
390     * to for this tracker. Tracker.move makes the changes to OBlocks to
391     * indicate the new occupancy positions of the train. Upon return,
392     * update the listeners for the trains next move.
393     */
394    private synchronized void processTrackerStateChange(Tracker tracker, OBlock block, int state) {
395        List<OBlock> oldRange = tracker.makeRange();// total range in effect when state change was detected
396        if (tracker.move(block, state)) {   // new total range has been made after move was done.
397            if (tracker._statusMessage != null) {
398                _frame.setStatus(tracker._statusMessage);
399            } else {
400                block._entryTime = System.currentTimeMillis();
401                adjustBlockListeners(oldRange, tracker.makeRange(), tracker);
402                _frame.setStatus(Bundle.getMessage("TrackerBlockEnter",
403                        tracker.getTrainName(), block.getDisplayName()));
404            }
405        } else {
406            if (tracker._statusMessage != null) {
407                _frame.setStatus(tracker._statusMessage);
408            } else if (_trackerList.contains(tracker)) {
409                adjustBlockListeners(oldRange, tracker.makeRange(), tracker);
410                long et = (System.currentTimeMillis() - block._entryTime) / 1000;
411                _frame.setStatus(Bundle.getMessage("TrackerBlockLeave", tracker.getTrainName(),
412                        block.getDisplayName(), et / 60, et % 60));
413            }
414        }
415    }
416
417    private void stopTrain(Tracker t, OBlock b) {
418        t.stop();
419        removeBlockListeners(t);
420        synchronized(this) {
421            _trackerList.remove(t);
422        }
423        long et = (System.currentTimeMillis() - t._startTime) / 1000;
424        String location;
425        if (b!= null) {
426            location = b.getDisplayName(); 
427        } else {
428            location = Bundle.getMessage("BeanStateUnknown");
429        }
430        _frame.setStatus(Bundle.getMessage("TrackerStopped", 
431                t.getTrainName(), location, et / 60, et % 60));
432        _frame._model.fireTableDataChanged();
433    }
434
435    class ChooseTracker extends JDialog implements ListSelectionListener {
436        OBlock block;
437        List<Tracker> trackers;
438        int state;
439        JList<Tracker> _jList;
440        
441        ChooseTracker(OBlock b, List<Tracker> ts, int s) {
442            super(_frame);
443            setTitle(Bundle.getMessage("TrackerTitle"));
444            block = b;
445            trackers = ts;
446            state = s;
447            JPanel contentPanel = new JPanel();
448            contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
449
450            contentPanel.add(Box.createVerticalStrut(STRUT_SIZE));
451            JPanel panel = new JPanel();
452            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
453            panel.add(new JLabel(Bundle.getMessage("MultipleTrackers", block.getDisplayName())));
454            panel.add(new JLabel(Bundle.getMessage("ChooseTracker", block.getDisplayName())));
455            JPanel p = new JPanel();
456            p.add(panel);
457            contentPanel.add(p);
458            panel = new JPanel();
459            panel.setBorder(javax.swing.BorderFactory .createLineBorder(Color.black, 2));
460            _jList = new JList<>();
461            _jList.setModel(new TrackerListModel());
462            _jList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
463            _jList.addListSelectionListener(this);
464            panel.add(_jList);
465            p = new JPanel();
466            p.add(panel);
467            contentPanel.add(p);
468            
469            contentPanel.add(Box.createVerticalStrut(STRUT_SIZE));
470            panel = new JPanel();
471            JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel"));
472            cancelButton.addActionListener((ActionEvent a) -> dispose());
473            panel.add(cancelButton);
474
475            contentPanel.add(panel);
476            setContentPane(contentPanel);
477            pack();
478            setLocation(_frame.getLocation());
479            setAlwaysOnTop(true);
480            setVisible(true);
481        }
482
483        @Override
484        public void valueChanged(ListSelectionEvent e) {
485            Tracker tr = _jList.getSelectedValue();
486            if (tr != null) {
487                processTrackerStateChange(tr, block, state);
488                dispose();
489            }
490        }
491
492        void checkClose(OBlock b) {
493            if (block.equals(b)) {
494                dispose();
495            }
496        }
497
498        class TrackerListModel extends AbstractListModel<Tracker> {
499            @Override
500            public int getSize() {
501                return trackers.size();
502            }
503
504            @Override
505            public Tracker getElementAt(int index) {
506                return trackers.get(index);
507            }
508        }
509    }
510
511    /**
512     * Holds a table of Trackers that follow adjacent occupancy. Needs to be a
513     * singleton to be opened and closed for trackers to report to it.
514     *
515     * @author Peter Cressman
516     */
517    class TableFrame extends JmriJFrame implements MouseListener {
518
519        private final TrackerTableModel _model;
520        private JmriJFrame _pickFrame;
521        JDialog _dialog;
522        JTextField _trainNameBox = new JTextField(30);
523        JTextField _trainLocationBox = new JTextField(30);
524        JTextField _status = new JTextField(80);
525        ArrayList<String> _statusHistory = new ArrayList<>();
526        public int _maxHistorySize = 20;
527
528        TableFrame() {
529            super(true, true);
530            setTitle(Bundle.getMessage("TrackerTable"));
531            _model = new TrackerTableModel();
532            JTable table = new JTable(_model);
533            TableRowSorter<TrackerTableModel> sorter = new TableRowSorter<>(_model);
534            table.setRowSorter(sorter);
535            table.getColumnModel().getColumn(TrackerTableModel.STOP_COL).setCellEditor(new ButtonEditor(new JButton()));
536            table.getColumnModel().getColumn(TrackerTableModel.STOP_COL).setCellRenderer(new ButtonRenderer());
537            for (int i = 0; i < _model.getColumnCount(); i++) {
538                int width = _model.getPreferredWidth(i);
539                table.getColumnModel().getColumn(i).setPreferredWidth(width);
540            }
541            table.setDragEnabled(true);
542            table.setTransferHandler(new jmri.util.DnDTableExportHandler());
543            JScrollPane tablePane = new JScrollPane(table);
544            Dimension dim = table.getPreferredSize();
545            int height = new JButton("STOPIT").getPreferredSize().height;
546            dim.height = height * 2;
547            tablePane.getViewport().setPreferredSize(dim);
548
549            JPanel tablePanel = new JPanel();
550            tablePanel.setLayout(new BoxLayout(tablePanel, BoxLayout.Y_AXIS));
551            JLabel title = new JLabel(Bundle.getMessage("TrackerTable"));
552            tablePanel.add(title, BorderLayout.NORTH);
553            tablePanel.add(tablePane, BorderLayout.CENTER);
554
555            JPanel panel = new JPanel();
556            JPanel p = new JPanel();
557            p.add(new JLabel(Bundle.getMessage("lastEvent")));
558            p.add(_status);
559            _status.setEditable(false);
560            _status.setBackground(Color.white);
561            _status.addMouseListener(this);
562            panel.add(p);
563
564            tablePanel.add(makeButtonPanel(), BorderLayout.CENTER);
565            tablePanel.add(panel, BorderLayout.CENTER);
566
567            setContentPane(tablePanel);
568
569            JMenuBar menuBar = new JMenuBar();
570            JMenu optionMenu = new JMenu(Bundle.getMessage("MenuMoreOptions"));
571            optionMenu.add(makePathRequirement());
572
573            JMenuItem pickerMenu = new JMenuItem(Bundle.getMessage("MenuBlockPicker"));
574            pickerMenu.addActionListener((ActionEvent a) -> openPickList());
575            optionMenu.add(pickerMenu);
576
577            optionMenu.add(WarrantTableAction.getDefault().makeLogMenu());
578            menuBar.add(optionMenu);
579            setJMenuBar(menuBar);
580            addHelpMenu("package.jmri.jmrit.logix.Tracker", true);
581
582            addWindowListener(new java.awt.event.WindowAdapter() {
583                @Override
584                public void windowClosing(java.awt.event.WindowEvent e) {
585                    setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
586                    if (_pickFrame != null) {
587                        _pickFrame.dispose();
588                    }
589                    _model.fireTableDataChanged();
590                }
591            });
592            setLocation(0, 100);
593            setVisible(true);
594            pack();
595        }
596
597        private JPanel makeButtonPanel() {
598            JPanel panel = new JPanel();
599            JButton button = new JButton(Bundle.getMessage("MenuNewTracker", "..."));
600            button.addActionListener((ActionEvent a) -> newTrackerDialog());
601            panel.add(button);
602
603            button = new JButton(Bundle.getMessage("MenuRefresh"));
604            button.addActionListener((ActionEvent a) -> _model.fireTableDataChanged());
605            panel.add(button);
606
607            return panel;
608        }
609
610        
611        
612        private JMenuItem makePathRequirement() {
613            JMenu pathkMenu = new JMenu(Bundle.getMessage("MenuPathRanking"));
614            ButtonGroup pathButtonGroup = new ButtonGroup();
615            JRadioButtonMenuItem r;
616            r = new JRadioButtonMenuItem(Bundle.getMessage("showAllTrackers"));
617            r.addActionListener((ActionEvent e) -> _requirePaths = false);
618            pathButtonGroup.add(r);
619            r.setSelected(!_requirePaths);
620            pathkMenu.add(r);
621
622            r = new JRadioButtonMenuItem(Bundle.getMessage("showMostLikely"));
623            r.addActionListener((ActionEvent e) -> _requirePaths = true);
624            pathButtonGroup.add(r);
625            r.setSelected(_requirePaths);
626            pathkMenu.add(r);
627
628            return pathkMenu;
629        }
630
631        protected boolean mouseClickedOnBlock(OBlock block) {
632            if (_dialog != null) {
633                if ((block.getState() & Block.OCCUPIED) != 0 && block.getValue() != null) {
634                    markNewTracker(block, (String) block.getValue(), null);
635                    return true;
636                }
637                _trainLocationBox.setText(block.getDisplayName());
638                if (block.getValue() != null) {
639                    _trainNameBox.setText((String) block.getValue());
640                }
641                return true;
642            }
643            return false;
644        }
645
646        private void newTrackerDialog() {
647            _dialog = new JDialog(this, Bundle.getMessage("MenuNewTracker", ""), false);
648            JPanel panel = new JPanel();
649            panel.setLayout(new BorderLayout(10, 10));
650            JPanel mainPanel = new JPanel();
651            mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
652
653            mainPanel.add(Box.createVerticalStrut(STRUT_SIZE));
654            JPanel p = new JPanel();
655            p.add(new JLabel(Bundle.getMessage("createTracker")));
656            mainPanel.add(p);
657
658            mainPanel.add(Box.createVerticalStrut(STRUT_SIZE));
659            mainPanel.add(makeTrackerNamePanel());
660            mainPanel.add(Box.createVerticalStrut(STRUT_SIZE));
661            mainPanel.add(makeDoneButtonPanel());
662            panel.add(mainPanel);
663            _dialog.getContentPane().add(panel);
664            _dialog.setLocation(this.getLocation().x + 100, this.getLocation().y + 100);
665            _dialog.pack();
666            _dialog.setVisible(true);
667        }
668
669        private JPanel makeTrackerNamePanel() {
670            _trainNameBox.setText("");
671            _trainLocationBox.setText("");
672            JPanel namePanel = new JPanel();
673            namePanel.setLayout(new BoxLayout(namePanel, BoxLayout.Y_AXIS));
674            JPanel p = new JPanel();
675            p.setLayout(new java.awt.GridBagLayout());
676            java.awt.GridBagConstraints c = new java.awt.GridBagConstraints();
677            c.gridwidth = 1;
678            c.gridheight = 1;
679            c.gridx = 0;
680            c.gridy = 0;
681            c.anchor = java.awt.GridBagConstraints.EAST;
682            p.add(new JLabel(Bundle.getMessage("TrainName")), c);
683            c.gridy = 1;
684            p.add(new JLabel(Bundle.getMessage("TrainLocation")), c);
685            c.gridx = 1;
686            c.gridy = 0;
687            c.anchor = java.awt.GridBagConstraints.WEST;
688            c.weightx = 1.0;
689            c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
690            p.add(_trainNameBox, c);
691            _trainNameBox.setToolTipText(Bundle.getMessage("TrackerNameTip"));
692            c.gridy = 1;
693            p.add(_trainLocationBox, c);
694            _trainLocationBox.setToolTipText(Bundle.getMessage("TrackerLocTip"));
695            namePanel.add(p);
696            return namePanel;
697        }
698
699        private JPanel makeDoneButtonPanel() {
700            JPanel buttonPanel = new JPanel();
701            JPanel panel0 = new JPanel();
702            panel0.setLayout(new FlowLayout());
703            JButton doneButton;
704            doneButton = new JButton(Bundle.getMessage("ButtonCreate"));
705            doneButton.addActionListener((ActionEvent a) -> {
706                    if (doDoneAction()) {
707                        _dialog.dispose();
708                        _dialog = null;
709                    }
710                });
711            panel0.add(doneButton);
712            buttonPanel.add(panel0);
713            return buttonPanel;
714        }
715
716        private boolean doDoneAction() {
717            boolean retOK = false;
718            String blockName = _trainLocationBox.getText();
719            if (blockName != null) {
720                OBlock block = InstanceManager.getDefault(OBlockManager.class).getOBlock(blockName);
721                if (block == null) {
722                    JOptionPane.showMessageDialog(this, Bundle.getMessage("BlockNotFound", blockName),
723                            Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
724                } else {
725                    retOK = makeTracker(block, _trainNameBox.getText(), null);
726                }
727            }
728            return retOK;
729        }
730
731        void openPickList() {
732            _pickFrame = new JmriJFrame();
733            JPanel content = new JPanel();
734            content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
735
736            JPanel blurb = new JPanel();
737            blurb.setLayout(new BoxLayout(blurb, BoxLayout.Y_AXIS));
738            blurb.add(Box.createVerticalStrut(ItemPalette.STRUT_SIZE));
739            blurb.add(new JLabel(Bundle.getMessage("DragBlockName")));
740            blurb.add(Box.createVerticalStrut(ItemPalette.STRUT_SIZE));
741            JPanel panel = new JPanel();
742            panel.add(blurb);
743            content.add(panel);
744            PickListModel[] models = {PickListModel.oBlockPickModelInstance()};
745            content.add(new PickPanel(models));
746
747            _pickFrame.setContentPane(content);
748            _pickFrame.setLocationRelativeTo(this);
749            _pickFrame.toFront();
750            _pickFrame.setVisible(true);
751            _pickFrame.pack();
752        }
753
754        @Override
755        public void mouseClicked(MouseEvent event) {
756            javax.swing.JPopupMenu popup = new javax.swing.JPopupMenu();
757            for (int i = _statusHistory.size() - 1; i >= 0; i--) {
758                popup.add(_statusHistory.get(i));
759            }
760            popup.show(_status, 0, 0);
761        }
762
763        @Override
764        public void mousePressed(MouseEvent event) {
765           // only handling mouseClicked
766        }
767
768        @Override
769        public void mouseEntered(MouseEvent event) {
770            // only handling mouseClicked
771        }
772
773        @Override
774        public void mouseExited(MouseEvent event) {
775            // only handling mouseClicked
776        }
777
778        @Override
779        public void mouseReleased(MouseEvent event) {
780            // only handling mouseClicked
781        }
782
783        private void setStatus(String msg) {
784            _status.setText(msg);
785            if (msg != null && msg.length() > 0) {
786                WarrantTableAction.getDefault().writetoLog(msg);
787                _statusHistory.add(msg);
788                while (_statusHistory.size() > _maxHistorySize) {
789                    _statusHistory.remove(0);
790                }
791            }
792        }
793    }
794
795    private class TrackerTableModel extends AbstractTableModel {
796
797        public static final int NAME_COL = 0;
798        public static final int STATUS_COL = 1;
799        public static final int STOP_COL = 2;
800        public static final int NUMCOLS = 3;
801
802        public TrackerTableModel() {
803            super();
804        }
805
806        @Override
807        public int getColumnCount() {
808            return NUMCOLS;
809        }
810
811        @Override
812        public synchronized int getRowCount() {
813            return _trackerList.size();
814        }
815
816        @Override
817        public String getColumnName(int col) {
818            switch (col) {
819                case NAME_COL:
820                    return Bundle.getMessage("TrainName");
821                case STATUS_COL:
822                    return Bundle.getMessage("status");
823                default:
824                    // fall out
825                    break;
826            }
827            return "";
828        }
829
830        @Override
831        public Object getValueAt(int rowIndex, int columnIndex) {
832            switch (columnIndex) {
833                case NAME_COL:
834                    return _trackerList.get(rowIndex).getTrainName();
835                case STATUS_COL:
836                    return _trackerList.get(rowIndex).getStatus();
837                case STOP_COL:
838                    return Bundle.getMessage("Stop");
839                default:
840                    // fall out
841                    break;
842            }
843            return "";
844        }
845
846        @Override
847        public void setValueAt(Object value, int row, int col) {
848            if (col == STOP_COL) {
849                Tracker t = _trackerList.get(row);
850                stopTrain(t, t.getHeadBlock());
851                fireTableDataChanged();
852            }
853        }
854
855        @Override
856        public boolean isCellEditable(int row, int col) {
857            return (col == STOP_COL);
858        }
859
860        @Override
861        public Class<?> getColumnClass(int col) {
862            if (col == STOP_COL) {
863                return JButton.class;
864            }
865            return String.class;
866        }
867
868        public int getPreferredWidth(int col) {
869            switch (col) {
870                case NAME_COL:
871                    return new JTextField(20).getPreferredSize().width;
872                case STATUS_COL:
873                    return new JTextField(60).getPreferredSize().width;
874                case STOP_COL:
875                    return new JButton("STOPIT").getPreferredSize().width;
876                default:
877                    // fall out
878                    break;
879            }
880            return 5;
881        }
882
883    }
884
885    private static final Logger log = LoggerFactory.getLogger(TrackerTableAction.class);
886
887    @ServiceProvider(service = InstanceInitializer.class)
888    public static class Initializer extends AbstractInstanceInitializer {
889
890        @Override
891        @Nonnull
892        public <T> Object getDefault(Class<T> type) {
893            if (type.equals(TrackerTableAction.class)) {
894                return new TrackerTableAction(Bundle.getMessage("MenuTrackers"));
895            }
896            return super.getDefault(type);
897        }
898
899        @Override
900        @Nonnull
901        public Set<Class<?>> getInitalizes() {
902            Set<Class<?>> set = super.getInitalizes();
903            set.add(TrackerTableAction.class);
904            return set;
905        }
906    }
907
908}