001package jmri.jmrit.operations.automation.gui; 002 003import java.awt.BorderLayout; 004import java.awt.FlowLayout; 005import java.awt.event.ActionEvent; 006import java.awt.event.ActionListener; 007import java.beans.PropertyChangeEvent; 008import java.beans.PropertyChangeListener; 009import java.util.ArrayList; 010import java.util.List; 011 012import javax.swing.*; 013import javax.swing.table.TableCellEditor; 014import javax.swing.table.TableColumnModel; 015 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019import jmri.InstanceManager; 020import jmri.jmrit.operations.automation.Automation; 021import jmri.jmrit.operations.automation.AutomationItem; 022import jmri.jmrit.operations.automation.actions.Action; 023import jmri.jmrit.operations.routes.RouteLocation; 024import jmri.jmrit.operations.setup.Control; 025import jmri.jmrit.operations.trains.Train; 026import jmri.jmrit.operations.trains.TrainManager; 027import jmri.util.table.ButtonEditor; 028import jmri.util.table.ButtonRenderer; 029 030/** 031 * Table Model for edit of a automation used by operations 032 * 033 * @author Daniel Boudreau Copyright (C) 2016 034 */ 035public class AutomationTableModel extends javax.swing.table.AbstractTableModel implements PropertyChangeListener { 036 037 protected static final String POINTER = " -->"; 038 039 // Defines the columns 040 private static final int ID_COLUMN = 0; 041 private static final int CURRENT_COLUMN = ID_COLUMN + 1; 042 private static final int ACTION_COLUMN = CURRENT_COLUMN + 1; 043 private static final int TRAIN_COLUMN = ACTION_COLUMN + 1; 044 private static final int ROUTE_COLUMN = TRAIN_COLUMN + 1; 045 private static final int AUTOMATION_COLUMN = ROUTE_COLUMN + 1; 046 private static final int STATUS_COLUMN = AUTOMATION_COLUMN + 1; 047 private static final int HIAF_COLUMN = STATUS_COLUMN + 1; 048 private static final int MESSAGE_COLUMN = HIAF_COLUMN + 1; 049 private static final int UP_COLUMN = MESSAGE_COLUMN + 1; 050 private static final int DOWN_COLUMN = UP_COLUMN + 1; 051 private static final int DELETE_COLUMN = DOWN_COLUMN + 1; 052 053 private static final int HIGHEST_COLUMN = DELETE_COLUMN + 1; 054 055 public AutomationTableModel() { 056 super(); 057 } 058 059 Automation _automation; 060 JTable _table; 061 AutomationTableFrame _frame; 062 boolean _matchMode = false; 063 064 private void updateList() { 065 if (_automation == null) { 066 return; 067 } 068 // first, remove listeners from the individual objects 069 removePropertyChangeAutomationItems(); 070 _list = _automation.getItemsBySequenceList(); 071 // and add them back in 072 for (AutomationItem item : _list) { 073 item.addPropertyChangeListener(this); 074 } 075 } 076 077 List<AutomationItem> _list = new ArrayList<>(); 078 079 protected void initTable(AutomationTableFrame frame, JTable table, Automation automation) { 080 _automation = automation; 081 _table = table; 082 _frame = frame; 083 084 // add property listeners 085 if (_automation != null) { 086 _automation.addPropertyChangeListener(this); 087 } 088 initTable(table); 089 } 090 091 private void initTable(JTable table) { 092 // Install the button handlers 093 TableColumnModel tcm = table.getColumnModel(); 094 ButtonRenderer buttonRenderer = new ButtonRenderer(); 095 TableCellEditor buttonEditor = new ButtonEditor(new javax.swing.JButton()); 096 tcm.getColumn(MESSAGE_COLUMN).setCellRenderer(buttonRenderer); 097 tcm.getColumn(MESSAGE_COLUMN).setCellEditor(buttonEditor); 098 tcm.getColumn(UP_COLUMN).setCellRenderer(buttonRenderer); 099 tcm.getColumn(UP_COLUMN).setCellEditor(buttonEditor); 100 tcm.getColumn(DOWN_COLUMN).setCellRenderer(buttonRenderer); 101 tcm.getColumn(DOWN_COLUMN).setCellEditor(buttonEditor); 102 tcm.getColumn(DELETE_COLUMN).setCellRenderer(buttonRenderer); 103 tcm.getColumn(DELETE_COLUMN).setCellEditor(buttonEditor); 104 table.setDefaultRenderer(JComboBox.class, new jmri.jmrit.symbolicprog.ValueRenderer()); 105 table.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor()); 106 107 // set column preferred widths 108 table.getColumnModel().getColumn(ID_COLUMN).setPreferredWidth(35); 109 table.getColumnModel().getColumn(CURRENT_COLUMN).setPreferredWidth(60); 110 table.getColumnModel().getColumn(ACTION_COLUMN).setPreferredWidth(200); 111 table.getColumnModel().getColumn(TRAIN_COLUMN).setPreferredWidth(200); 112 table.getColumnModel().getColumn(ROUTE_COLUMN).setPreferredWidth(200); 113 table.getColumnModel().getColumn(AUTOMATION_COLUMN).setPreferredWidth(200); 114 table.getColumnModel().getColumn(STATUS_COLUMN).setPreferredWidth(70); 115 table.getColumnModel().getColumn(HIAF_COLUMN).setPreferredWidth(50); 116 table.getColumnModel().getColumn(MESSAGE_COLUMN).setPreferredWidth(70); 117 table.getColumnModel().getColumn(UP_COLUMN).setPreferredWidth(60); 118 table.getColumnModel().getColumn(DOWN_COLUMN).setPreferredWidth(70); 119 table.getColumnModel().getColumn(DELETE_COLUMN).setPreferredWidth(70); 120 121 _frame.loadTableDetails(table); 122 // does not use a table sorter 123 table.setRowSorter(null); 124 125 updateList(); 126 } 127 128 @Override 129 public int getRowCount() { 130 return _list.size(); 131 } 132 133 @Override 134 public int getColumnCount() { 135 return HIGHEST_COLUMN; 136 } 137 138 @Override 139 public String getColumnName(int col) { 140 switch (col) { 141 case ID_COLUMN: 142 return Bundle.getMessage("Id"); 143 case CURRENT_COLUMN: 144 return Bundle.getMessage("Current"); 145 case ACTION_COLUMN: 146 return Bundle.getMessage("Action"); 147 case TRAIN_COLUMN: 148 return Bundle.getMessage("Train"); 149 case ROUTE_COLUMN: 150 return Bundle.getMessage("RouteLocation"); 151 case AUTOMATION_COLUMN: 152 return Bundle.getMessage("AutomationOther"); 153 case STATUS_COLUMN: 154 return Bundle.getMessage("Status"); 155 case MESSAGE_COLUMN: 156 return Bundle.getMessage("Message"); 157 case HIAF_COLUMN: 158 return Bundle.getMessage("HaltIfActionFails"); 159 case UP_COLUMN: 160 return Bundle.getMessage("Up"); 161 case DOWN_COLUMN: 162 return Bundle.getMessage("Down"); 163 case DELETE_COLUMN: 164 return Bundle.getMessage("ButtonDelete"); 165 default: 166 return "unknown"; // NOI18N 167 } 168 } 169 170 @Override 171 public Class<?> getColumnClass(int col) { 172 switch (col) { 173 case ID_COLUMN: 174 return String.class; 175 case CURRENT_COLUMN: 176 return String.class; 177 case ACTION_COLUMN: 178 return JComboBox.class; 179 case TRAIN_COLUMN: 180 return JComboBox.class; 181 case ROUTE_COLUMN: 182 return JComboBox.class; 183 case AUTOMATION_COLUMN: 184 return JComboBox.class; 185 case STATUS_COLUMN: 186 return String.class; 187 case HIAF_COLUMN: 188 return Boolean.class; 189 case MESSAGE_COLUMN: 190 return JButton.class; 191 case UP_COLUMN: 192 return JButton.class; 193 case DOWN_COLUMN: 194 return JButton.class; 195 case DELETE_COLUMN: 196 return JButton.class; 197 default: 198 return null; 199 } 200 } 201 202 @Override 203 public boolean isCellEditable(int row, int col) { 204 switch (col) { 205 case CURRENT_COLUMN: 206 case ACTION_COLUMN: 207 case TRAIN_COLUMN: 208 case ROUTE_COLUMN: 209 case AUTOMATION_COLUMN: 210 case UP_COLUMN: 211 case DOWN_COLUMN: 212 case DELETE_COLUMN: 213 return true; 214 case HIAF_COLUMN: { 215 AutomationItem item = _list.get(row); 216 return item.getAction().isMessageFailEnabled(); 217 } 218 case MESSAGE_COLUMN: { 219 AutomationItem item = _list.get(row); 220 JComboBox<Action> acb = getActionComboBox(item); 221 return ((Action) acb.getSelectedItem()).isMessageOkEnabled(); 222 } 223 default: 224 return false; 225 } 226 } 227 228 // TODO adding synchronized to the following causes thread lock. 229 // See line in propertyChange below: 230 // _table.scrollRectToVisible(_table.getCellRect(row, 0, true)); 231 // Stack trace: 232 // owns: Component$AWTTreeLock (id=127) 233 // waiting for: AutomationTableModel (id=128) 234 // AutomationTableModel.getRowCount() line: 131 235 // JTable.getRowCount() line: 2662 236 // BasicTableUI.paint(Graphics, JComponent) line: 1766 237 // BasicTableUI(ComponentUI).update(Graphics, JComponent) line: 161 238 // JTable(JComponent).paintComponent(Graphics) line: 777 239 // JTable(JComponent).paint(Graphics) line: 1053 240 // JViewport(JComponent).paintChildren(Graphics) line: 886 241 // JViewport(JComponent).paint(Graphics) line: 1062 242 // JViewport.paint(Graphics) line: 692 243 // JViewport(JComponent).paintToOffscreen(Graphics, int, int, int, int, int, int) line: 5223 244 // RepaintManager$PaintManager.paintDoubleBuffered(JComponent, Image, Graphics, int, int, int, int) line: 1572 245 // RepaintManager$PaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1495 246 // RepaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1265 247 // JViewport(JComponent).paintForceDoubleBuffered(Graphics) line: 1089 248 // JViewport.paintView(Graphics) line: 1635 249 // JViewport.flushViewDirtyRegion(Graphics, Rectangle) line: 1508 250 // JViewport.setViewPosition(Point) line: 1093 251 // JViewport.scrollRectToVisible(Rectangle) line: 436 252 // JTable(JComponent).scrollRectToVisible(Rectangle) line: 3108 253 // AutomationTableModel.propertyChange(PropertyChangeEvent) line: 498 254 // PropertyChangeSupport.fire(PropertyChangeListener[], PropertyChangeEvent) line: 335 255 // PropertyChangeSupport.firePropertyChange(PropertyChangeEvent) line: 327 256 // PropertyChangeSupport.firePropertyChange(String, Object, Object) line: 263 257 // Automation.setDirtyAndFirePropertyChange(String, Object, Object) line: 666 258 // Automation.setCurrentAutomationItem(AutomationItem) line: 279 259 // Automation.setNextAutomationItem() line: 243 260 // Automation.CheckForActionPropertyChange(PropertyChangeEvent) line: 607 261 // Automation.propertyChange(PropertyChangeEvent) line: 646 262 // PropertyChangeSupport.fire(PropertyChangeListener[], PropertyChangeEvent) line: 335 263 // PropertyChangeSupport.firePropertyChange(PropertyChangeEvent) line: 327 264 // PropertyChangeSupport.firePropertyChange(String, Object, Object) line: 263 265 // ResetTrainAction(Action).firePropertyChange(String, Object, Object) line: 244 266 // ResetTrainAction(Action).finishAction(boolean, Object[]) line: 158 267 // ResetTrainAction(Action).finishAction(boolean) line: 128 268 // ResetTrainAction.doAction() line: 27 269 // Automation$1.run() line: 172 270 // Thread.run() line: 745 271 272 @Override 273 public Object getValueAt(int row, int col) { 274 if (row >= getRowCount()) { 275 return "ERROR row " + row; // NOI18N 276 } 277 AutomationItem item = _list.get(row); 278 if (item == null) { 279 return "ERROR automation item unknown " + row; // NOI18N 280 } 281 switch (col) { 282 case ID_COLUMN: 283 return item.getId(); 284 case CURRENT_COLUMN: 285 return getCurrentPointer(row, item); 286 case ACTION_COLUMN: 287 return getActionComboBox(item); 288 case TRAIN_COLUMN: 289 return getTrainComboBox(item); 290 case ROUTE_COLUMN: 291 return getRouteLocationComboBox(item); 292 case AUTOMATION_COLUMN: 293 return getAutomationComboBox(item); 294 case STATUS_COLUMN: 295 return getStatus(item); 296 case HIAF_COLUMN: 297 return item.isHaltFailureEnabled() & item.getAction().isMessageFailEnabled(); 298 case MESSAGE_COLUMN: 299 if (item.getMessage().equals(AutomationItem.NONE) && item.getMessageFail().equals(AutomationItem.NONE)) 300 return Bundle.getMessage("Add"); 301 else 302 return Bundle.getMessage("ButtonEdit"); 303 case UP_COLUMN: 304 return Bundle.getMessage("Up"); 305 case DOWN_COLUMN: 306 return Bundle.getMessage("Down"); 307 case DELETE_COLUMN: 308 return Bundle.getMessage("ButtonDelete"); 309 default: 310 return "unknown " + col; // NOI18N 311 } 312 } 313 314 @Override 315 public void setValueAt(Object value, int row, int col) { 316 if (value == null) { 317 log.debug("Warning automation table row {} still in edit", row); 318 return; 319 } 320 AutomationItem item = _list.get(row); 321 switch (col) { 322 case CURRENT_COLUMN: 323 setCurrent(item); 324 break; 325 case ACTION_COLUMN: 326 setAction(value, item); 327 break; 328 case TRAIN_COLUMN: 329 setTrain(value, item); 330 break; 331 case ROUTE_COLUMN: 332 setRouteLocation(value, item); 333 break; 334 case AUTOMATION_COLUMN: 335 setAutomationColumn(value, item); 336 break; 337 case HIAF_COLUMN: 338 item.setHaltFailureEnabled(((Boolean) value).booleanValue()); 339 break; 340 case MESSAGE_COLUMN: 341 setMessage(value, item); 342 break; 343 case UP_COLUMN: 344 moveUpAutomationItem(item); 345 break; 346 case DOWN_COLUMN: 347 moveDownAutomationItem(item); 348 break; 349 case DELETE_COLUMN: 350 deleteAutomationItem(item); 351 break; 352 default: 353 break; 354 } 355 } 356 357 private String getCurrentPointer(int row, AutomationItem item) { 358 if (_automation.getCurrentAutomationItem() == item) { 359 return POINTER; 360 } else { 361 return ""; 362 } 363 } 364 365 private JComboBox<Action> getActionComboBox(AutomationItem item) { 366 JComboBox<Action> cb = AutomationItem.getActionComboBox(); 367 // cb.setSelectedItem(item.getAction()); TODO understand why this didn't work, class? 368 for (int index = 0; index < cb.getItemCount(); index++) { 369 // select the action based on its action code 370 if (item.getAction() != null && (cb.getItemAt(index)).getCode() == item.getAction().getCode()) { 371 cb.setSelectedIndex(index); 372 break; 373 } 374 } 375 return cb; 376 } 377 378 private JComboBox<Train> getTrainComboBox(AutomationItem item) { 379 JComboBox<Train> cb = InstanceManager.getDefault(TrainManager.class).getTrainComboBox(); 380 cb.setSelectedItem(item.getTrain()); 381 // determine if train combo box is enabled 382 cb.setEnabled(item.getAction() != null && item.getAction().isTrainMenuEnabled()); 383 return cb; 384 } 385 386 private JComboBox<RouteLocation> getRouteLocationComboBox(AutomationItem item) { 387 JComboBox<RouteLocation> cb = new JComboBox<>(); 388 if (item.getTrain() != null && item.getTrain().getRoute() != null) { 389 cb = item.getTrain().getRoute().getComboBox(); 390 cb.setSelectedItem(item.getRouteLocation()); 391 } 392 // determine if route combo box is enabled 393 cb.setEnabled(item.getAction() != null && item.getAction().isRouteMenuEnabled()); 394 return cb; 395 } 396 397 /** 398 * Returns either a comboBox loaded with Automations, or a goto list of 399 * AutomationItems, or TrainSchedules. 400 * 401 * @return comboBox loaded with automations or a goto automationIem list 402 */ 403 private JComboBox<?> getAutomationComboBox(AutomationItem item) { 404 if (item.getAction() != null) { 405 return item.getAction().getComboBox(); 406 } 407 return null; 408 } 409 410 private String getStatus(AutomationItem item) { 411 return item.getStatus(); 412 } 413 414 private void setCurrent(AutomationItem item) { 415 _automation.setCurrentAutomationItem(item); 416 _automation.resetAutomationItems(item); 417 } 418 419 private void setAction(Object value, AutomationItem item) { 420 @SuppressWarnings("unchecked") 421 JComboBox<Action> cb = (JComboBox<Action>) value; 422 item.setAction((Action) cb.getSelectedItem()); 423 } 424 425 private void setTrain(Object value, AutomationItem item) { 426 @SuppressWarnings("unchecked") 427 JComboBox<Train> cb = (JComboBox<Train>) value; 428 item.setTrain((Train) cb.getSelectedItem()); 429 } 430 431 private void setRouteLocation(Object value, AutomationItem item) { 432 @SuppressWarnings("unchecked") 433 JComboBox<RouteLocation> cb = (JComboBox<RouteLocation>) value; 434 item.setRouteLocation((RouteLocation) cb.getSelectedItem()); 435 } 436 437 private void setAutomationColumn(Object value, AutomationItem item) { 438 item.setOther(((JComboBox<?>) value).getSelectedItem()); 439 } 440 441 private void setMessage(Object value, AutomationItem item) { 442 // Create comment panel 443 final JDialog dialog = new JDialog(); 444 dialog.setLayout(new BorderLayout()); 445 dialog.setTitle(Bundle.getMessage("Message")); 446 447 final JTextArea messageTextArea = new JTextArea(6, 100); 448 JScrollPane messageScroller = new JScrollPane(messageTextArea); 449 messageScroller.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("MessageOk"))); 450 dialog.add(messageScroller, BorderLayout.NORTH); 451 messageTextArea.setText(item.getMessage()); 452 messageTextArea.setToolTipText(Bundle.getMessage("TipMessage")); 453 454 JPanel buttonPane = new JPanel(); 455 buttonPane.setLayout(new FlowLayout(FlowLayout.CENTER)); 456 dialog.add(buttonPane, BorderLayout.SOUTH); 457 458 JCheckBox haltCheckBox = new JCheckBox(Bundle.getMessage("HaltIfFail")); 459 haltCheckBox.setSelected(item.isHaltFailureEnabled()); 460 461 final JTextArea messageFailTextArea = new JTextArea(6, 100); 462 if (item.getAction() != null && item.getAction().isMessageFailEnabled()) { 463 JScrollPane messageFailScroller = new JScrollPane(messageFailTextArea); 464 messageFailScroller.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("MessageFail"))); 465 dialog.add(messageFailScroller, BorderLayout.CENTER); 466 messageFailTextArea.setText(item.getMessageFail()); 467 messageFailTextArea.setToolTipText(Bundle.getMessage("TipMessage")); 468 469 buttonPane.add(haltCheckBox); 470 buttonPane.add(new JLabel(" ")); // some padding 471 } 472 473 JButton okayButton = new JButton(Bundle.getMessage("ButtonOK")); 474 okayButton.addActionListener(new ActionListener() { 475 @Override 476 public void actionPerformed(ActionEvent arg0) { 477 item.setMessage(messageTextArea.getText()); 478 item.setMessageFail(messageFailTextArea.getText()); 479 item.setHaltFailureEnabled(haltCheckBox.isSelected()); 480 dialog.dispose(); 481 return; 482 } 483 }); 484 buttonPane.add(okayButton); 485 486 JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel")); 487 cancelButton.addActionListener(new ActionListener() { 488 @Override 489 public void actionPerformed(ActionEvent arg0) { 490 dialog.dispose(); 491 return; 492 } 493 }); 494 buttonPane.add(cancelButton); 495 496 JButton defaultMessagesButton = new JButton(Bundle.getMessage("DefaultMessages")); 497 defaultMessagesButton.setToolTipText(Bundle.getMessage("TipDefaultButton")); 498 defaultMessagesButton.addActionListener(new ActionListener() { 499 @Override 500 public void actionPerformed(ActionEvent arg0) { 501 if (messageTextArea.getText().equals(AutomationItem.NONE)) { 502 messageTextArea.setText(Bundle.getMessage("DefaultMessageOk")); 503 } 504 if (messageFailTextArea.getText().equals(AutomationItem.NONE)) { 505 messageFailTextArea.setText(Bundle.getMessage("DefaultMessageFail")); 506 } 507 return; 508 } 509 }); 510 buttonPane.add(defaultMessagesButton); 511 512 dialog.setModal(true); 513 dialog.pack(); 514 dialog.setVisible(true); 515 } 516 517 private void moveUpAutomationItem(AutomationItem item) { 518 log.debug("move automation item up"); 519 _automation.moveItemUp(item); 520 } 521 522 private void moveDownAutomationItem(AutomationItem item) { 523 log.debug("move automation item down"); 524 _automation.moveItemDown(item); 525 } 526 527 private void deleteAutomationItem(AutomationItem item) { 528 log.debug("Delete automation item"); 529 _automation.deleteItem(item); 530 } 531 532 // this table listens for changes to a automation and its car types 533 @Override 534 public void propertyChange(PropertyChangeEvent e) { 535 if (Control.SHOW_PROPERTY) 536 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e 537 .getNewValue()); 538 539 if (e.getPropertyName().equals(Automation.LISTCHANGE_CHANGED_PROPERTY)) { 540 updateList(); 541 fireTableDataChanged(); 542 } 543 if (e.getPropertyName().equals(Automation.CURRENT_ITEM_CHANGED_PROPERTY)) { 544 SwingUtilities.invokeLater(() -> { 545 int row = _list.indexOf(_automation.getCurrentAutomationItem()); 546 int viewRow = _table.convertRowIndexToView(row); 547 // the following line can be responsible for a thread lock 548 _table.scrollRectToVisible(_table.getCellRect(viewRow, 0, true)); 549 fireTableDataChanged(); 550 }); 551 } 552 // update automation item? 553 if (e.getSource().getClass().equals(AutomationItem.class)) { 554 AutomationItem item = (AutomationItem) e.getSource(); 555 int row = _list.indexOf(item); 556 if (Control.SHOW_PROPERTY) 557 log.debug("Update automation item table row: {}", row); 558 if (row >= 0) { 559 fireTableRowsUpdated(row, row); 560 } 561 } 562 } 563 564 private void removePropertyChangeAutomationItems() { 565 for (AutomationItem item : _list) { 566 item.removePropertyChangeListener(this); 567 } 568 } 569 570 public void dispose() { 571 if (_automation != null) { 572 removePropertyChangeAutomationItems(); 573 _automation.removePropertyChangeListener(this); 574 } 575 } 576 577 private final static Logger log = LoggerFactory.getLogger(AutomationTableModel.class); 578}