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}