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