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