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