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;
018
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    protected static final String ramp = Bundle.getMessage("SmoothHalt");
059    protected static final String stop = Bundle.getMessage("Stop");
060    protected static final String estop = Bundle.getMessage("EStop");
061    protected static final String resume = Bundle.getMessage("Resume");
062    protected static final String speedup = Bundle.getMessage("SpeedUp");
063    protected static final String abort = Bundle.getMessage("Abort");
064    protected static final String retryfwd = Bundle.getMessage("MoveToNext");
065    protected 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
077    private final WarrantTableModel _model;
078
079    /**
080     * Get the default instance of a Warrant table window.
081     *
082     * @return the default instance; creating it if necessary
083     */
084    public static WarrantTableFrame getDefault() {
085        WarrantTableFrame instance = InstanceManager.getOptionalDefault(WarrantTableFrame.class).orElseGet(() -> {
086            WarrantTableFrame newInstance =
087                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        JScrollPane 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((ActionEvent e) -> WarrantTableAction.getDefault().makeNXFrame());
187
188        JButton haltAllButton = new JButton(Bundle.getMessage("HaltAllTrains"));
189        haltAllButton.addActionListener((ActionEvent e) -> haltAllAction());
190        haltAllButton.setForeground(Color.RED);
191
192        JPanel footerLeft = new JPanel();
193        footerLeft.setLayout(new BorderLayout());
194        footerLeft.add(nxButton, BorderLayout.LINE_START);
195        footerLeft.add(statusLabel, BorderLayout.LINE_END);
196
197        JPanel footer = new JPanel();
198        footer.setLayout(new BorderLayout());
199        footer.add(footerLeft, BorderLayout.LINE_START);
200        footer.add(_status, BorderLayout.CENTER);
201        footer.add(haltAllButton, BorderLayout.LINE_END);
202
203        Container pane = getContentPane();
204        pane.add(title, BorderLayout.PAGE_START);
205        pane.add(tablePane, BorderLayout.CENTER);
206        pane.add(footer, BorderLayout.PAGE_END);
207
208        addWindowListener(new java.awt.event.WindowAdapter() {
209            @Override
210            public void windowClosing(java.awt.event.WindowEvent e) {
211                if (_concatDialog !=null) {
212                    _concatDialog.dispose();
213                }
214                _model.dispose();
215                dispose();
216            }
217        });
218
219        JMenuBar menuBar = new JMenuBar();
220        JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
221        fileMenu.add(new jmri.configurexml.StoreMenu());
222        JMenu warrantMenu = new JMenu(Bundle.getMessage("MenuWarrant"));
223        warrantMenu.add(new AbstractAction(Bundle.getMessage("ConcatWarrants")) {
224            @Override
225            public void actionPerformed(ActionEvent e) {
226                concatMenuAction();
227            }
228        });
229
230        warrantMenu.add(new AbstractAction(Bundle.getMessage("CreateWarrant")) {
231            @Override
232            public void actionPerformed(ActionEvent e) {
233                WarrantTableAction.getDefault().makeWarrantFrame(null, null);
234            }
235         });
236        warrantMenu.add(InstanceManager.getDefault(TrackerTableAction.class));
237        warrantMenu.add(new AbstractAction(Bundle.getMessage("CreateNXWarrant")) {
238
239            @Override
240            public void actionPerformed(ActionEvent e) {
241                WarrantTableAction.getDefault().makeNXFrame();
242            }
243        });
244        warrantMenu.add(WarrantTableAction.getDefault().makeLogMenu());
245        menuBar.add(warrantMenu);
246        setJMenuBar(menuBar);
247        addHelpMenu("package.jmri.jmrit.logix.WarrantTable", true);
248
249        pack();
250    }
251
252    private void haltAllAction() {
253        _model.haltAllTrains();
254    }
255
256    private void concatMenuAction() {
257        _concatDialog = new JDialog(this, Bundle.getMessage("ConcatWarrants"), false);
258        JPanel mainPanel = new JPanel();
259        mainPanel.setLayout(new BorderLayout(5, 5));
260        JPanel panel = new JPanel();
261        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
262        JPanel pp = new JPanel();
263        pp.setLayout(new FlowLayout());
264        pp.add(new JLabel("A:"));
265        pp.add(_startWarrant);
266        _startWarrant.setDragEnabled(true);
267        _startWarrant.setTransferHandler(new jmri.util.DnDStringImportHandler());
268        panel.add(pp);
269        pp = new JPanel();
270        pp.setLayout(new FlowLayout());
271        pp.add(new JLabel("B:"));
272        pp.add(_endWarrant);
273        _endWarrant.setDragEnabled(true);
274        _endWarrant.setTransferHandler(new jmri.util.DnDStringImportHandler());
275        panel.add(pp);
276        JButton concatButton = new JButton(Bundle.getMessage("Concatenate"));
277        concatButton.addActionListener((ActionEvent e) ->
278            concatenate(_startWarrant.getText(), _endWarrant.getText()) );
279        panel.add(concatButton, Component.CENTER_ALIGNMENT);
280
281        mainPanel.add(panel);
282        _concatDialog.getContentPane().add(mainPanel);
283        _concatDialog.setLocation(getLocation().x + 200, getLocation().y + 200);
284        _concatDialog.pack();
285        _concatDialog.setVisible(true);
286    }
287
288    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "OPath extends Path")
289    private void concatenate(String startName, String endName) {
290        WarrantManager manager = InstanceManager.getDefault(jmri.jmrit.logix.WarrantManager.class);
291        Warrant startW = manager.getWarrant(startName.trim());
292        Warrant endW = manager.getWarrant(endName.trim());
293        if (startW == null || endW == null) {
294            showWarning("BadWarrantNames");
295            return;
296        }
297        BlockOrder last = startW.getLastOrder();
298        BlockOrder next = endW.getfirstOrder();
299        if (last == null || next == null) {
300            showWarning("EmptyRoutes");
301            return;
302        }
303        if (!last.getBlock().equals(next.getBlock())) {
304            showWarning("BlocksDontMatch");
305            return;
306        }
307        if (!last.getPathName().equals(next.getPathName())) {
308            boolean foundPath = false;
309            String entryName = last.getEntryName();
310            String exitName = next.getExitName();
311            Iterator<Path> iter = last.getBlock().getPaths().iterator();
312            while (iter.hasNext()) {
313                String pathName = ((OPath)iter.next()).getName();
314                if (pathName.equals(entryName) && pathName.equals(exitName)) {
315                    last.setPathName(pathName);
316                    foundPath = true;
317                    break;
318                }
319            }
320            if (!foundPath) {
321                showWarning("RoutesDontMatch");
322                return;
323            }
324        }
325        WarrantTableAction.getDefault().makeWarrantFrame(startW, endW);
326        _concatDialog.dispose();
327    }
328
329    protected boolean askStopQuestion(String blockName) {
330        boolean includeAllCmds = false;
331        if (JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("stopAtBlock", blockName),
332                Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION,
333                JmriJOptionPane.QUESTION_MESSAGE) == JmriJOptionPane.YES_OPTION) {
334            includeAllCmds = true;
335        }
336        return includeAllCmds;
337    }
338
339    public void showWarning(String msg) {
340        setVisible(true);
341        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage(msg, _startWarrant.getText(), _endWarrant.getText()),
342                Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
343    }
344
345    /**
346     * *********************** Table ***************************************
347     */
348    private static class RouteBoxCellEditor extends DefaultCellEditor {
349
350        RouteBoxCellEditor(JComboBox<String> comboBox) {
351            super(comboBox);
352            comboBox.setFont(new Font(null, Font.PLAIN, 12));
353        }
354
355        @Override
356        public Component getTableCellEditorComponent(JTable table, Object value,
357                boolean isSelected, int r, int column) {
358            TableModel m = table.getModel();
359            WarrantTableModel model = null;
360            if (m instanceof WarrantTableModel) {
361                model = (WarrantTableModel) m;
362            }
363            if (model == null) {
364                log.error("Unexpected table model: {}", m );
365            }
366
367            // If table has been sorted, table row no longer is the same as array index
368            int row = r;
369            if (table.getRowSorter() != null) {
370                row = table.convertRowIndexToModel(row);
371            }
372            Warrant warrant = null;
373            if (model != null) {
374                warrant = model.getWarrantAt(row);
375            }
376            if (warrant == null) {
377                log.warn("getWarrantAt row= {}, Warrant is null!", row);
378                return getComponent();
379            }
380            Component component = getComponent();
381            if (component instanceof JComboBox<?>) {
382                @SuppressWarnings("unchecked")
383                JComboBox<String> comboBox = (JComboBox<String>) component;
384                comboBox.removeAllItems();
385
386                List<BlockOrder> orders = warrant.getBlockOrders();
387                for (int i = 0; i < orders.size(); i++) {
388                    BlockOrder order = orders.get(i);
389                    comboBox.addItem(order.getBlock().getDisplayName() + ": - " + order.getPath().getName());
390                }
391            } else {
392                log.error("Unexpected editor component: {}", component );
393            }
394            return component;
395        }
396    }
397
398    private static class ControlBoxCellEditor extends DefaultCellEditor {
399
400        ControlBoxCellEditor(JComboBox<String> comboBox) {
401            super(comboBox);
402            comboBox.setFont(new Font(null, Font.PLAIN, 12));
403         }
404
405        @Override
406        public Component getTableCellEditorComponent(JTable table, Object value,
407                boolean isSelected, int r, int column) {
408            Component component = getComponent();
409            if (component instanceof JComboBox<?>) {
410                @SuppressWarnings("unchecked")
411                JComboBox<String> comboBox = (JComboBox<String>) component;
412                comboBox.removeItemAt(0);
413                comboBox.insertItemAt((String)value, 0);
414                comboBox.setSelectedIndex(0);
415                if (log.isDebugEnabled()) {
416                    // If table has been sorted, table row no longer is the same as array index
417                    int row = r;
418                    if (table.getRowSorter() != null) {
419                        row = table.convertRowIndexToModel(row);
420                    }
421                    WarrantTableModel model = (WarrantTableModel)table.getModel();
422                    Warrant warrant = model.getWarrantAt(row);
423                    log.debug("getTableCellEditorComponent warrant= {}, selection= {}",
424                            warrant.getDisplayName(), comboBox.getSelectedItem());
425                }
426            } else {
427                log.error("Unexpected editor component: {}", component );
428            }
429            return component;
430        }
431    }
432
433    private long lastClicktime; // keep double clicks from showing dialogs
434
435    /**
436     * Return error message if warrant cannot be run.
437     *
438     * @param w    warrant
439     * @param mode running type
440     * @return null if warrant is started
441     */
442    public String runTrain(Warrant w, int mode) {
443        long time = System.currentTimeMillis();
444        if (time - lastClicktime < 1000) {
445            return null;
446        }
447        lastClicktime = time;
448
449        String msg = null;
450        WarrantFrame frame = WarrantTableAction.getDefault().getOpenFrame();
451        if (frame != null) {
452            Warrant warrant = frame.getWarrant();
453            if ( warrant != null && w.equals(warrant) && frame.isRunning() ) {
454                msg = Bundle.getMessage("CannotRun", w.getDisplayName(),
455                    Bundle.getMessage("TrainRunning", warrant.getTrainName()));
456            }
457        }
458
459        if (msg == null) {
460            msg = _model.checkAddressInUse(w);
461        }
462
463        if (msg == null) {
464            msg = w.checkforTrackers();
465        }
466
467        if (msg == null) {
468            msg = w.setRunMode(mode, null, null, null, w.getRunBlind());
469            if (msg != null) {
470                w.deAllocate();
471            }
472        }
473        if (msg != null) {
474            return Bundle.getMessage("CannotRun", w.getDisplayName(), msg);
475        }
476        return null;
477    }
478
479    @Override
480    public void mouseClicked(JmriMouseEvent event) {
481        int clicks = event.getClickCount();
482        if (clicks > 1) {
483            StringBuilder sb = new StringBuilder();
484            for (int i = _statusHistory.size() - 1; i >= 0; i--) {
485                sb.append(_statusHistory.get(i));
486                sb.append('\n');
487            }
488            Transferable transferable = new StringSelection(sb.toString());
489            Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
490            cb.setContents(transferable, null);
491
492        } else {
493            JPopupMenu popup = new JPopupMenu();
494            for (int i = _statusHistory.size() - 1; i >= 0; i--) {
495                popup.add(_statusHistory.get(i));
496            }
497            popup.show(_status, 0, 0);
498        }
499    }
500
501    @Override
502    public void mousePressed(JmriMouseEvent event) {
503    }
504    @Override
505    public void mouseEntered(JmriMouseEvent event) {
506    }
507    @Override
508    public void mouseExited(JmriMouseEvent event) {
509    }
510    @Override
511    public void mouseReleased(JmriMouseEvent event) {
512    }
513
514    void setStatusText(String msg, Color c, boolean save) {
515        _status.setForeground(c);
516        _status.setText(msg);
517        if (save && msg != null && msg.length() > 0) {
518            _statusHistory.add(msg);
519            WarrantTableAction.getDefault().writetoLog(msg);
520            while (_statusHistory.size() > _maxHistorySize) {
521                _statusHistory.remove(0);
522            }
523        }
524    }
525
526    protected String getStatus() {
527        return _status.getText();
528    }
529
530    private static final String BLANK = "                                                                                                 ";
531
532    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WarrantTableFrame.class);
533
534}