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}