001package jmri.jmrit.logix;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.BorderLayout;
006import java.awt.Color;
007import java.awt.Component;
008import java.awt.Container;
009import java.awt.event.ComponentAdapter;
010import java.awt.event.ComponentEvent;
011import java.awt.FlowLayout;
012import java.awt.Font;
013import java.awt.Toolkit;
014import java.awt.datatransfer.Clipboard;
015import java.awt.datatransfer.StringSelection;
016import java.awt.datatransfer.Transferable;
017import java.awt.event.ActionEvent;
018import java.awt.event.ActionListener;
019import java.util.ArrayList;
020import java.util.Iterator;
021import java.util.List;
022
023import javax.swing.*;
024import javax.swing.table.TableModel;
025import javax.swing.table.TableRowSorter;
026
027import jmri.InstanceManager;
028import jmri.Path;
029import jmri.util.swing.JmriJOptionPane;
030import jmri.util.swing.JmriMouseEvent;
031import jmri.util.swing.JmriMouseListener;
032import jmri.util.swing.XTableColumnModel;
033import jmri.util.table.ButtonEditor;
034import jmri.util.table.ButtonRenderer;
035
036/**
037 * The WarrantTableFrame lists the existing Warrants and has controls to set
038 * their routes, train IDs launch them and control their running (halt, resume,
039 * abort. etc.
040 *
041 * The WarrantTableFrame also can initiate NX (eNtry/eXit) warrants
042 * <br>
043 * <hr>
044 * This file is part of JMRI.
045 * <p>
046 * JMRI is free software; you can redistribute it and/or modify it under the
047 * terms of version 2 of the GNU General Public License as published by the Free
048 * Software Foundation. See the "COPYING" file for a copy of this license.
049 * <p>
050 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
051 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
052 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
053 *
054 * @author Pete Cressman Copyright (C) 2009, 2010
055 */
056public class WarrantTableFrame extends jmri.util.JmriJFrame implements JmriMouseListener {
057
058    static final String ramp = Bundle.getMessage("SmoothHalt");
059    static final String stop = Bundle.getMessage("Stop");
060    static final String estop = Bundle.getMessage("EStop");
061    static final String resume = Bundle.getMessage("Resume");
062    static final String speedup = Bundle.getMessage("SpeedUp");
063    static final String abort = Bundle.getMessage("Abort");
064    static final String retryfwd = Bundle.getMessage("MoveToNext");
065    static final String retrybkwd = Bundle.getMessage("MoveToPrevious");    // removed from drop down
066    static final String[] controls = {" ", ramp, resume, stop, speedup, retryfwd, estop, abort,
067                            (org.slf4j.LoggerFactory.getLogger(WarrantTableFrame.class).isDebugEnabled()?"Debug":"")};
068
069    public static int _maxHistorySize = 40;
070
071    private final JTextField _startWarrant = new JTextField(30);
072    private final JTextField _endWarrant = new JTextField(30);
073    private JDialog _concatDialog;
074    private final JTextField _status = new JTextField(90);
075    private final ArrayList<String> _statusHistory = new ArrayList<>();
076    private JScrollPane _tablePane;
077
078    private final WarrantTableModel _model;
079
080    /**
081     * Get the default instance of a Warrant table window.
082     *
083     * @return the default instance; creating it if necessary
084     */
085    public static WarrantTableFrame getDefault() {
086        WarrantTableFrame instance = InstanceManager.getOptionalDefault(WarrantTableFrame.class).orElseGet(() -> {
087            WarrantTableFrame newInstance = InstanceManager.setDefault(WarrantTableFrame.class, new WarrantTableFrame());
088            try {
089                newInstance.initComponents();
090            } catch (Exception ex) {
091                log.error("Unable to initilize Warrant Table Frame", ex);
092            }
093            return newInstance;
094        });
095        if (jmri.util.ThreadingUtil.isGUIThread()) {
096            instance.setVisible(true);
097        }
098        return instance;
099    }
100
101    protected WarrantTableModel getModel() {
102        return _model;
103    }
104
105    private WarrantTableFrame() {
106        super(true, true);
107        setTitle(Bundle.getMessage("WarrantTable"));
108        _model = new WarrantTableModel(this);
109        _model.init();
110
111    }
112
113    /**
114     * By default, Swing components should be created an installed in this
115     * method, rather than in the ctor itself.
116     */
117    @Override
118    public void initComponents() {
119
120        log.debug("initComponents");
121        //Casts at getTableCellEditorComponent() now fails with 3.0 ??
122        JTable table = new JTable(_model);
123        TableRowSorter<WarrantTableModel> sorter = new TableRowSorter<>(_model);
124        table.setRowSorter(sorter);
125        // Use XTableColumnModel so we can control which columns are visible
126        XTableColumnModel tcm = new XTableColumnModel();
127        table.setColumnModel(tcm);
128        table.getTableHeader().setReorderingAllowed(true);
129        table.createDefaultColumnsFromModel();
130        _model.addHeaderListener(table);
131
132        JComboBox<String> cbox = new JComboBox<>();
133        RouteBoxCellEditor comboEd = new RouteBoxCellEditor(cbox);
134        ControlBoxCellEditor controlEd = new ControlBoxCellEditor(new JComboBox<>(controls));
135
136        table.setDefaultRenderer(Boolean.class, new ButtonRenderer());
137        table.setDefaultRenderer(JComboBox.class, new jmri.jmrit.symbolicprog.ValueRenderer());
138        table.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor());
139
140        table.getColumnModel().getColumn(WarrantTableModel.CONTROL_COLUMN).setCellEditor(controlEd);
141        table.getColumnModel().getColumn(WarrantTableModel.ROUTE_COLUMN).setCellEditor(comboEd);
142        table.getColumnModel().getColumn(WarrantTableModel.ALLOCATE_COLUMN).setCellEditor(new ButtonEditor(new JButton()));
143        table.getColumnModel().getColumn(WarrantTableModel.ALLOCATE_COLUMN).setCellRenderer(new ButtonRenderer());
144        table.getColumnModel().getColumn(WarrantTableModel.DEALLOC_COLUMN).setCellEditor(new ButtonEditor(new JButton()));
145        table.getColumnModel().getColumn(WarrantTableModel.DEALLOC_COLUMN).setCellRenderer(new ButtonRenderer());
146        table.getColumnModel().getColumn(WarrantTableModel.AUTO_RUN_COLUMN).setCellEditor(new ButtonEditor(new JButton()));
147        table.getColumnModel().getColumn(WarrantTableModel.AUTO_RUN_COLUMN).setCellRenderer(new ButtonRenderer());
148        table.getColumnModel().getColumn(WarrantTableModel.MANUAL_RUN_COLUMN).setCellEditor(new ButtonEditor(new JButton()));
149        table.getColumnModel().getColumn(WarrantTableModel.MANUAL_RUN_COLUMN).setCellRenderer(new ButtonRenderer());
150        table.getColumnModel().getColumn(WarrantTableModel.EDIT_COLUMN).setCellEditor(new ButtonEditor(new JButton()));
151        table.getColumnModel().getColumn(WarrantTableModel.EDIT_COLUMN).setCellRenderer(new ButtonRenderer());
152        table.getColumnModel().getColumn(WarrantTableModel.DELETE_COLUMN).setCellEditor(new ButtonEditor(new JButton()));
153        table.getColumnModel().getColumn(WarrantTableModel.DELETE_COLUMN).setCellRenderer(new ButtonRenderer());
154        //table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
155        for (int i = 0; i < _model.getColumnCount(); i++) {
156            int width = _model.getPreferredWidth(i);
157            table.getColumnModel().getColumn(i).setPreferredWidth(width);
158        }
159        tcm.setColumnVisible(tcm.getColumnByModelIndex(WarrantTableModel.MANUAL_RUN_COLUMN), false);
160
161        int rowHeight = comboEd.getComponent().getPreferredSize().height;
162        table.setRowHeight(rowHeight);
163
164        table.setDragEnabled(true);
165        table.setTransferHandler(new jmri.util.DnDTableExportHandler());
166        table.addComponentListener(new ComponentAdapter() {
167            @Override
168            public void componentResized(ComponentEvent e) {
169                int lastIndex = table.getRowCount()-1;
170                table.changeSelection(lastIndex, 0,false,false);
171            }
172        });
173        _tablePane = new JScrollPane(table);
174
175        JLabel title = new JLabel(Bundle.getMessage("ShowWarrants"));
176        title.setHorizontalAlignment(SwingConstants.CENTER);
177
178        JLabel statusLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("status")));
179        _status.addMouseListener(JmriMouseListener.adapt(this));
180        _status.setBackground(Color.white);
181        _status.setFont(_status.getFont().deriveFont(Font.BOLD));
182        _status.setEditable(false);
183        _status.setText(BLANK.substring(0, 90));
184
185        JButton nxButton = new JButton(Bundle.getMessage("CreateNXWarrant"));
186        nxButton.addActionListener(new ActionListener() {
187            @Override
188            public void actionPerformed(ActionEvent e) {
189                WarrantTableAction.getDefault().makeNXFrame();
190            }
191        });
192
193        JButton haltAllButton = new JButton(Bundle.getMessage("HaltAllTrains"));
194        haltAllButton.addActionListener(new ActionListener() {
195            @Override
196            public void actionPerformed(ActionEvent e) {
197                haltAllAction();
198            }
199        });
200        haltAllButton.setForeground(Color.RED);
201
202        JPanel footerLeft = new JPanel();
203        footerLeft.setLayout(new BorderLayout());
204        footerLeft.add(nxButton, BorderLayout.LINE_START);
205        footerLeft.add(statusLabel, BorderLayout.LINE_END);
206
207        JPanel footer = new JPanel();
208        footer.setLayout(new BorderLayout());
209        footer.add(footerLeft, BorderLayout.LINE_START);
210        footer.add(_status, BorderLayout.CENTER);
211        footer.add(haltAllButton, BorderLayout.LINE_END);
212
213        Container pane = getContentPane();
214        pane.add(title, BorderLayout.PAGE_START);
215        pane.add(_tablePane, BorderLayout.CENTER);
216        pane.add(footer, BorderLayout.PAGE_END);
217
218        addWindowListener(new java.awt.event.WindowAdapter() {
219            @Override
220            public void windowClosing(java.awt.event.WindowEvent e) {
221                if (_concatDialog !=null) {
222                    _concatDialog.dispose();
223                }
224                _model.dispose();
225                dispose();
226            }
227        });
228
229        JMenuBar menuBar = new JMenuBar();
230        JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
231        fileMenu.add(new jmri.configurexml.StoreMenu());
232        JMenu warrantMenu = new JMenu(Bundle.getMessage("MenuWarrant"));
233        warrantMenu.add(new AbstractAction(Bundle.getMessage("ConcatWarrants")) {
234            @Override
235            public void actionPerformed(ActionEvent e) {
236                concatMenuAction();
237            }
238        });
239//        warrantMenu.add(new jmri.jmrit.logix.WarrantTableAction("CreateWarrant"));
240        warrantMenu.add(new AbstractAction(Bundle.getMessage("CreateWarrant")) {
241            @Override
242            public void actionPerformed(ActionEvent e) {
243                WarrantTableAction.getDefault().makeWarrantFrame(null, null);
244            }
245         });
246        warrantMenu.add(InstanceManager.getDefault(TrackerTableAction.class));
247        warrantMenu.add(new AbstractAction(Bundle.getMessage("CreateNXWarrant")) {
248
249            @Override
250            public void actionPerformed(ActionEvent e) {
251                WarrantTableAction.getDefault().makeNXFrame();
252            }
253        });
254        warrantMenu.add(WarrantTableAction.getDefault().makeLogMenu());
255        menuBar.add(warrantMenu);
256        setJMenuBar(menuBar);
257        addHelpMenu("package.jmri.jmrit.logix.WarrantTable", true);
258
259        pack();
260    }
261
262    private void haltAllAction() {
263        _model.haltAllTrains();
264    }
265
266    private void concatMenuAction() {
267        _concatDialog = new JDialog(this, Bundle.getMessage("ConcatWarrants"), false);
268        JPanel mainPanel = new JPanel();
269        mainPanel.setLayout(new BorderLayout(5, 5));
270        JPanel panel = new JPanel();
271        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
272        JPanel pp = new JPanel();
273        pp.setLayout(new FlowLayout());
274        pp.add(new JLabel("A:"));
275        pp.add(_startWarrant);
276        _startWarrant.setDragEnabled(true);
277        _startWarrant.setTransferHandler(new jmri.util.DnDStringImportHandler());
278        panel.add(pp);
279        pp = new JPanel();
280        pp.setLayout(new FlowLayout());
281        pp.add(new JLabel("B:"));
282        pp.add(_endWarrant);
283        _endWarrant.setDragEnabled(true);
284        _endWarrant.setTransferHandler(new jmri.util.DnDStringImportHandler());
285        panel.add(pp);
286        JButton concatButton = new JButton(Bundle.getMessage("Concatenate"));
287        concatButton.addActionListener(new ActionListener() {
288            @Override
289            public void actionPerformed(ActionEvent e) {
290                concatenate(_startWarrant.getText(), _endWarrant.getText());
291            }
292        });
293        panel.add(concatButton, Box.CENTER_ALIGNMENT);
294
295        mainPanel.add(panel);
296        _concatDialog.getContentPane().add(mainPanel);
297        _concatDialog.setLocation(getLocation().x + 200, getLocation().y + 200);
298        _concatDialog.pack();
299        _concatDialog.setVisible(true);
300    }
301
302    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path")
303    private void concatenate(String startName, String endName) {
304        WarrantManager manager = InstanceManager.getDefault(jmri.jmrit.logix.WarrantManager.class);
305        Warrant startW = manager.getWarrant(startName.trim());
306        Warrant endW = manager.getWarrant(endName.trim());
307        if (startW == null || endW == null) {
308            showWarning("BadWarrantNames");
309            return;
310        }
311        BlockOrder last = startW.getLastOrder();
312        BlockOrder next = endW.getfirstOrder();
313        if (last == null || next == null) {
314            showWarning("EmptyRoutes");
315            return;
316        }
317        if (!last.getBlock().equals(next.getBlock())) {
318            showWarning("BlocksDontMatch");
319            return;
320        }
321        if (!last.getPathName().equals(next.getPathName())) {
322            boolean foundPath = false;
323            String entryName = last.getEntryName();
324            String exitName = next.getExitName();
325            Iterator<Path> iter = last.getBlock().getPaths().iterator();
326            while (iter.hasNext()) {
327                String pathName = ((OPath)iter.next()).getName();
328                if (pathName.equals(entryName) && pathName.equals(exitName)) {
329                    last.setPathName(pathName);
330                    foundPath = true;
331                    break;
332                }
333            }
334            if (!foundPath) {
335                showWarning("RoutesDontMatch");
336                return;
337            }
338        }
339        WarrantTableAction.getDefault().makeWarrantFrame(startW, endW);
340        _concatDialog.dispose();
341    }
342
343    protected boolean askStopQuestion(String blockName) {
344        boolean includeAllCmds = false;
345        if (JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("stopAtBlock", blockName),
346                Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION,
347                JmriJOptionPane.QUESTION_MESSAGE) == JmriJOptionPane.YES_OPTION) {
348            includeAllCmds = true;
349        }
350        return includeAllCmds;
351    }
352
353    public void showWarning(String msg) {
354        setVisible(true);
355        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage(msg, _startWarrant.getText(), _endWarrant.getText()),
356                Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
357    }
358
359    /**
360     * *********************** Table ***************************************
361     */
362    static public class RouteBoxCellEditor extends DefaultCellEditor {
363
364        RouteBoxCellEditor(JComboBox<String> comboBox) {
365            super(comboBox);
366            comboBox.setFont(new Font(null, Font.PLAIN, 12));
367        }
368
369        @Override
370        public Component getTableCellEditorComponent(JTable table, Object value,
371                boolean isSelected, int r, int column) {
372            TableModel m = table.getModel();
373            WarrantTableModel model = null;
374            if (m instanceof WarrantTableModel) {
375                model = (WarrantTableModel) m;
376            }
377            if (model == null) {
378                log.error("Unexpected table model: {}", m );
379            }
380
381            // If table has been sorted, table row no longer is the same as array index
382            int row = r;
383            if (table.getRowSorter() != null) {
384                row = table.convertRowIndexToModel(row);
385            }
386            Warrant warrant = null;
387            if (model != null) {
388                warrant = model.getWarrantAt(row);
389            }
390            if (warrant == null) {
391                log.warn("getWarrantAt row= {}, Warrant is null!", row);
392                return getComponent();
393            }
394            Component component = getComponent();
395            if (component instanceof JComboBox<?>) {
396                @SuppressWarnings("unchecked")
397                JComboBox<String> comboBox = (JComboBox<String>) component;
398                comboBox.removeAllItems();
399
400                List<BlockOrder> orders = warrant.getBlockOrders();
401                for (int i = 0; i < orders.size(); i++) {
402                    BlockOrder order = orders.get(i);
403                    comboBox.addItem(order.getBlock().getDisplayName() + ": - " + order.getPath().getName());
404                }
405            } else {
406                log.error("Unexpected editor component: {}", component );
407            }
408            return component;
409        }
410    }
411
412
413    static public class ControlBoxCellEditor extends DefaultCellEditor {
414
415        ControlBoxCellEditor(JComboBox<String> comboBox) {
416            super(comboBox);
417            comboBox.setFont(new Font(null, Font.PLAIN, 12));
418         }
419
420        @Override
421        public Component getTableCellEditorComponent(JTable table, Object value,
422                boolean isSelected, int r, int column) {
423            Component component = getComponent();
424            if (component instanceof JComboBox<?>) {
425                @SuppressWarnings("unchecked")
426                JComboBox<String> comboBox = (JComboBox<String>) component;
427                comboBox.removeItemAt(0);
428                comboBox.insertItemAt((String)value, 0);
429                comboBox.setSelectedIndex(0);
430                if (log.isDebugEnabled()) {
431                    // If table has been sorted, table row no longer is the same as array index
432                    int row = r;
433                    if (table.getRowSorter() != null) {
434                        row = table.convertRowIndexToModel(row);
435                    }
436                    WarrantTableModel model = (WarrantTableModel)table.getModel();
437                    Warrant warrant = model.getWarrantAt(row);
438                    log.debug("getTableCellEditorComponent warrant= {}, selection= {}",
439                            warrant.getDisplayName(), comboBox.getSelectedItem());
440                }
441            } else {
442                log.error("Unexpected editor component: {}", component );
443            }
444            return component;
445        }
446    }
447
448    long lastClicktime; // keep double clicks from showing dialogs
449    /**
450     * Return error message if warrant cannot be run.
451     *
452     * @param w    warrant
453     * @param mode running type
454     * @return null if warrant is started
455     */
456    public String runTrain(Warrant w, int mode) {
457        long time = System.currentTimeMillis();
458        if (time - lastClicktime < 1000) {
459            return null;
460        }
461        lastClicktime = time;
462
463        String msg = null;
464        WarrantFrame frame = WarrantTableAction.getDefault().getOpenFrame();
465        if (frame != null) {
466            Warrant warrant = frame.getWarrant();
467            if (warrant != null) {
468                if (w.equals(warrant) && frame.isRunning()) {
469                    msg = Bundle.getMessage("CannotRun", w.getDisplayName(),
470                            Bundle.getMessage("TrainRunning", warrant.getTrainName()));
471                }
472            }
473        }
474
475        if (msg == null) {
476            msg = _model.checkAddressInUse(w);
477        }
478
479        if (msg == null) {
480            msg = w.checkforTrackers();
481        }
482
483        if (msg == null) {
484            msg = w.setRunMode(mode, null, null, null, w.getRunBlind());
485            if (msg != null) {
486                w.deAllocate();
487            }
488        }
489        if (msg != null) {
490            return Bundle.getMessage("CannotRun", w.getDisplayName(), msg);
491        }
492        return null;
493    }
494
495    @Override
496    public void mouseClicked(JmriMouseEvent event) {
497        int clicks = event.getClickCount();
498        if (clicks > 1) {
499            StringBuilder sb = new StringBuilder();
500            for (int i = _statusHistory.size() - 1; i >= 0; i--) {
501                sb.append(_statusHistory.get(i));
502                sb.append('\n');
503            }
504            Transferable transferable = new StringSelection(sb.toString());
505            Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
506            cb.setContents(transferable, null);
507
508        } else {
509            javax.swing.JPopupMenu popup = new javax.swing.JPopupMenu();
510            for (int i = _statusHistory.size() - 1; i >= 0; i--) {
511                popup.add(_statusHistory.get(i));
512            }
513            popup.show(_status, 0, 0);
514        }
515    }
516
517    @Override
518    public void mousePressed(JmriMouseEvent event) {
519    }
520    @Override
521    public void mouseEntered(JmriMouseEvent event) {
522    }
523    @Override
524    public void mouseExited(JmriMouseEvent event) {
525    }
526    @Override
527    public void mouseReleased(JmriMouseEvent event) {
528    }
529
530    void setStatusText(String msg, Color c, boolean save) {
531        _status.setForeground(c);
532        _status.setText(msg);
533        if (save && msg != null && msg.length() > 0) {
534            _statusHistory.add(msg);
535            WarrantTableAction.getDefault().writetoLog(msg);
536            while (_statusHistory.size() > _maxHistorySize) {
537                _statusHistory.remove(0);
538            }
539        }
540    }
541
542    protected String getStatus() {
543        return _status.getText();
544    }
545
546    static String BLANK = "                                                                                                 ";
547
548    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WarrantTableFrame.class);
549
550}