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