001package jmri.jmrit.conditional; 002 003import java.awt.Component; 004import java.awt.Container; 005import java.awt.Dimension; 006import java.awt.FlowLayout; 007import java.awt.Font; 008import java.util.SortedSet; 009 010import javax.swing.*; 011import javax.swing.border.Border; 012import javax.swing.table.*; 013 014import jmri.*; 015 016import jmri.jmrit.conditional.ConditionalEditBase.SelectionMode; 017import jmri.jmrit.entryexit.EntryExitPairs; 018import jmri.jmrit.logix.OBlockManager; 019import jmri.jmrit.logix.WarrantManager; 020import jmri.util.swing.JmriJOptionPane; 021import jmri.util.table.ButtonEditor; 022import jmri.util.table.ButtonRenderer; 023 024/** 025 * Extracted from ConditionalEditList. 026 * Allows ConditionalEditList to open alternate frame 027 * for copying Conditionals. 028 * 029 * @author Pete Cressman Copyright (C) 2020 030 */ 031public class ConditionalCopyFrame extends ConditionalFrame { 032 033 CopyTableModel _actionTableModel = null; 034 CopyTableModel _variableTableModel = null; 035 JTextField _antecedentField; 036 JPanel _antecedentPanel; 037 038 Conditional.ItemType _saveType = Conditional.ItemType.NONE; 039 040 // ------------------------------------------------------------------ 041 042 ConditionalCopyFrame(String title, Conditional conditional, ConditionalList parent) { 043 super(title, conditional, parent); 044 makeConditionalFrame(conditional); 045 } 046 047 void makeConditionalFrame(Conditional conditional) { 048 addHelpMenu( 049 "package.jmri.jmrit.conditional.ConditionalCopy", true); // NOI18N 050 Container contentPane = getContentPane(); 051 contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); 052 contentPane.add(makeTopPanel(conditional)); 053 054 // add Logical Expression Section 055 JPanel logicPanel = new JPanel(); 056 logicPanel.setLayout(new BoxLayout(logicPanel, BoxLayout.Y_AXIS)); 057 058 // add Antecedent Expression Panel - ONLY appears for MIXED operator statements 059 _antecedentField = new JTextField(65); 060 _antecedentField.setText(ConditionalEditBase.translateAntecedent(_antecedent, false)); 061 _antecedentField.setFont(new Font("SansSerif", Font.BOLD, 14)); // NOI18N 062 _antecedentPanel = makeEditPanel(_antecedentField, "LabelAntecedent", "LabelAntecedentHint"); // NOI18N 063 _antecedentPanel.setVisible(_logicType == Conditional.AntecedentOperator.MIXED); 064 logicPanel.add(_antecedentPanel); 065 066 // add state variable table title 067 JPanel varTitle = new JPanel(); 068 varTitle.setLayout(new FlowLayout()); 069 varTitle.add(new JLabel(Bundle.getMessage("StateVariableTableTitle"))); // NOI18N 070 logicPanel.add(varTitle); 071 // initialize table of state variables 072 _variableTableModel = new VariableCopyTableModel(false, _parent._selectionMode != SelectionMode.USESINGLE); 073 JTable variableTable = new JTable(_variableTableModel); 074 variableTable.setRowSelectionAllowed(false); 075 int rowHeight = variableTable.getRowHeight(); 076 077 TableColumnModel variableColumnModel = variableTable.getColumnModel(); 078 079 TableColumn rowColumn = variableColumnModel.getColumn(VariableCopyTableModel.ROWNUM_COLUMN); 080 rowColumn.setResizable(false); 081 rowColumn.setMaxWidth(new JTextField(3).getPreferredSize().width); 082 083 TableColumn nameColumn = variableColumnModel.getColumn(VariableCopyTableModel.NAME_COLUMN); 084 nameColumn.setResizable(false); 085 if (_parent._selectionMode != SelectionMode.USESINGLE) { 086 nameColumn.setCellEditor(new NameCellEditor(new JComboBox<String>())); 087 } else { 088 nameColumn.setCellEditor(new NameCellEditor(new JTextField())); 089 } 090 nameColumn.setMinWidth(40); 091 nameColumn.setResizable(true); 092 093 TableColumn descColumn = variableColumnModel.getColumn(VariableCopyTableModel.DESCRIPTION_COLUMN); 094 descColumn.setMinWidth(300); 095 descColumn.setResizable(true); 096 097 // add a scroll pane 098 JScrollPane variableTableScrollPane = new JScrollPane(variableTable); 099 Dimension dim = variableTable.getPreferredSize(); 100 dim.height = 7 * rowHeight; 101 variableTableScrollPane.getViewport().setPreferredSize(dim); 102 103 logicPanel.add(variableTableScrollPane); 104 105 106 Border logicPanelBorder = BorderFactory.createEtchedBorder(); 107 Border logicPanelTitled = BorderFactory.createTitledBorder( 108 logicPanelBorder, Bundle.getMessage("TitleLogicalExpression") + " "); // NOI18N 109 logicPanel.setBorder(logicPanelTitled); 110 contentPane.add(logicPanel); 111 // End of Logic Expression Section 112 113 // add Action Consequents Section 114 JPanel conseqentPanel = new JPanel(); 115 conseqentPanel.setLayout(new BoxLayout(conseqentPanel, BoxLayout.Y_AXIS)); 116 117 JPanel actTitle = new JPanel(); 118 actTitle.setLayout(new FlowLayout()); 119 actTitle.add(new JLabel(Bundle.getMessage("ActionTableTitle"))); // NOI18N 120 conseqentPanel.add(actTitle); 121 122 // set up action consequents table 123 _actionTableModel = new ActionCopyTableModel(true, _parent._selectionMode != SelectionMode.USESINGLE); 124 JTable actionTable = new JTable(_actionTableModel); 125 actionTable.setRowSelectionAllowed(false); 126 ButtonRenderer buttonRenderer = new ButtonRenderer(); 127 actionTable.setDefaultRenderer(JButton.class, buttonRenderer); 128 TableCellEditor buttonEditor = new ButtonEditor(new JButton()); 129 actionTable.setDefaultEditor(JButton.class, buttonEditor); 130 JButton testButton = new JButton("XXXXXX"); // NOI18N 131 actionTable.setRowHeight(testButton.getPreferredSize().height); 132 TableColumnModel actionColumnModel = actionTable.getColumnModel(); 133 134 nameColumn = actionColumnModel.getColumn(ActionCopyTableModel.NAME_COLUMN); 135 nameColumn.setResizable(false); 136 if (_parent._selectionMode != SelectionMode.USESINGLE) { 137 nameColumn.setCellEditor(new NameCellEditor(new JComboBox<String>())); 138 } else { 139 nameColumn.setCellEditor(new NameCellEditor(new JTextField())); 140 } 141 nameColumn.setMinWidth(40); 142 nameColumn.setResizable(true); 143 144 descColumn = actionColumnModel.getColumn(ActionCopyTableModel.DESCRIPTION_COLUMN); 145 descColumn.setMinWidth(300); 146 descColumn.setResizable(true); 147 148 TableColumn deleteColumn = actionColumnModel.getColumn(ActionCopyTableModel.DELETE_COLUMN); 149 // ButtonRenderer and TableCellEditor already set 150 deleteColumn.setMinWidth(testButton.getPreferredSize().width); 151 deleteColumn.setMaxWidth(testButton.getPreferredSize().width); 152 deleteColumn.setResizable(false); 153 154 // add a scroll pane 155 JScrollPane actionTableScrollPane = new JScrollPane(actionTable); 156 dim = actionTableScrollPane.getPreferredSize(); 157 dim.height = 7 * rowHeight; 158 actionTableScrollPane.getViewport().setPreferredSize(dim); 159 conseqentPanel.add(actionTableScrollPane); 160 161 162 Border conseqentPanelBorder = BorderFactory.createEtchedBorder(); 163 Border conseqentPanelTitled = BorderFactory.createTitledBorder( 164 conseqentPanelBorder, Bundle.getMessage("TitleAction")); // NOI18N 165 conseqentPanel.setBorder(conseqentPanelTitled); 166 contentPane.add(conseqentPanel); 167 // End of Action Consequents Section 168 169 contentPane.add(_parent.makeBottomPanel()); 170 171 // setup window closing listener 172 this.addWindowListener( 173 new java.awt.event.WindowAdapter() { 174 @Override 175 public void windowClosing(java.awt.event.WindowEvent e) { 176 cancelConditionalPressed(); 177 } 178 }); 179 // initialize state variable table 180 _variableTableModel.fireTableDataChanged(); 181 // initialize action variables 182 _actionTableModel.fireTableDataChanged(); 183 } // end makeConditionalFrame 184 185 class NameCellEditor extends DefaultCellEditor { 186 187 NameCellEditor(JComboBox<String> comboBox) { 188 super(comboBox); 189 log.debug("New JComboBox<String> NameCellEditor"); 190 } 191 192 NameCellEditor(JTextField textField) { 193 super(textField); 194 log.debug("New JTextField NameCellEditor"); 195 } 196 197 @SuppressWarnings("unchecked") // getComponent call requires an unchecked cast 198 @Override 199 public Component getTableCellEditorComponent(JTable table, Object value, 200 boolean isSelected, int row, int column) { 201 CopyTableModel model = (CopyTableModel) table.getModel(); 202 if (log.isDebugEnabled()) { 203 log.debug("getTableCellEditorComponent: row= {}, column= {} selected = {} isComboTable= {}", 204 row, column, isSelected, model._isComboTable); 205 } 206 Conditional.ItemType itemType; 207 String name; 208 if (model.isActionTable()) { 209 ConditionalAction action = _actionList.get(row); 210 itemType = action.getType().getItemType(); 211 name = action.getDeviceName(); 212 } else { 213 ConditionalVariable variable = _variableList.get(row); 214 itemType = variable.getType().getItemType(); 215 name = variable.getName(); 216 } 217 if (model._isComboTable) { 218 SortedSet<NamedBean> namedBeans = (SortedSet<NamedBean>)getItemNamedBeamns(itemType); 219 JComboBox<String> comboBox = (JComboBox<String>)getComponent(); 220 comboBox.removeAllItems(); 221 for (NamedBean b : namedBeans) { 222 comboBox.addItem(b.getDisplayName()); 223 } 224 } else { 225 if (_saveType != itemType) { 226 _parent.closeSinglePanelPickList(); 227 _parent.createSinglePanelPickList(itemType, null, true); 228 _saveType = itemType; 229 } 230 JTextField field = (JTextField)getComponent(); 231 field.setText(name); 232 } 233 return super.getTableCellEditorComponent(table, value, isSelected, row, column); 234 } 235 } 236 237 SortedSet<?> getItemNamedBeamns(Conditional.ItemType itemType) { 238 switch (itemType) { 239 case SENSOR: // 1 240 return InstanceManager.getDefault(SensorManager.class).getNamedBeanSet(); 241 case TURNOUT: // 2 242 return InstanceManager.getDefault(TurnoutManager.class).getNamedBeanSet(); 243 case LIGHT: // 3 244 return InstanceManager.getDefault(LightManager.class).getNamedBeanSet(); 245 case SIGNALHEAD: // 4 246 return InstanceManager.getDefault(SignalHeadManager.class).getNamedBeanSet(); 247 case SIGNALMAST: // 5 248 return InstanceManager.getDefault(SignalMastManager.class).getNamedBeanSet(); 249 case MEMORY: // 6 250 return InstanceManager.getDefault(MemoryManager.class).getNamedBeanSet(); 251 case LOGIX: // 7 252 return InstanceManager.getDefault(LogixManager.class).getNamedBeanSet(); 253 case WARRANT: // 8 254 return InstanceManager.getDefault(WarrantManager.class).getNamedBeanSet(); 255 case OBLOCK: // 10 256 return InstanceManager.getDefault(OBlockManager.class).getNamedBeanSet(); 257 case ENTRYEXIT: // 11 258 return InstanceManager.getDefault(EntryExitPairs.class).getNamedBeanSet(); 259 case OTHER: // 14 260 return InstanceManager.getDefault(jmri.RouteManager.class).getNamedBeanSet(); 261 default: 262 return new java.util.TreeSet<String>(); // Skip any other items. 263 } 264 } 265 /** 266 * Respond to the Cancel button in the Edit Conditional frame. 267 * <p> 268 * Does the cleanup from updateConditionalPressed 269 * and _editConditionalFrame window closer. 270 */ 271 @Override 272 void cancelConditionalPressed() { 273 super.cancelConditionalPressed(); 274 } 275 276 /** 277 * Validate Variable name change. 278 * <p> 279 * Messages are sent to the user for any errors found. This routine returns 280 * false immediately after finding the first error, even if there might be 281 * more errors. 282 * 283 * @param name name of the ConditionalVariable 284 * @param variable ConditionalVariable to validate 285 * @return true if all data checks out OK, otherwise false 286 */ 287 boolean validateVariable(String name, ConditionalVariable variable) { 288 Conditional.ItemType itemType = variable.getType().getItemType(); 289 290 if (!checkIsAction(name, itemType) ) { 291 return false; 292 } 293 if (!isValidType(itemType, name)) { 294 return false; 295 } 296 return (true); 297 } 298 /** 299 * Validate Action item name change. 300 * <p> 301 * Messages are sent to the user for any errors found. This routine returns 302 * false immediately after finding an error, even if there might be more 303 * errors. 304 * 305 * @param name name of the action 306 * @param action ConditionalAction to validate 307 * @return true if all data checks out OK, otherwise false 308 */ 309 boolean validateAction(String name, ConditionalAction action) { 310 if (!checkReferenceByMemory(name)) { 311 return false; 312 } 313 Conditional.ItemType itemType = action.getType().getItemType(); 314 if (_referenceByMemory) { 315 if (itemType == Conditional.ItemType.MEMORY) { 316 JmriJOptionPane.showMessageDialog(this, 317 Bundle.getMessage("Warn6"), 318 Bundle.getMessage("WarningTitle"), // NOI18N 319 JmriJOptionPane.WARNING_MESSAGE); 320 return false; 321 } 322 } else { 323 if (!checkIsVariable(name, itemType) ) { 324 return false; 325 } 326 if (!isValidType(itemType, name)) { 327 return false; 328 } 329 } 330 return (true); 331 } 332 333 boolean isValidType( Conditional.ItemType itemType, String name) { 334 switch (itemType) { 335 case SENSOR: 336 name = _parent.validateSensorReference(name); 337 if (name == null) { 338 return false; 339 } 340 break; 341 case TURNOUT: 342 name = _parent.validateTurnoutReference(name); 343 if (name == null) { 344 return false; 345 } 346 break; 347 case CONDITIONAL: 348 name = _parent.validateConditionalReference(name); 349 if (name == null) { 350 return false; 351 } 352 break; 353 case LIGHT: 354 name = _parent.validateLightReference(name); 355 if (name == null) { 356 return false; 357 } 358 break; 359 case SIGNALHEAD: 360 name = _parent.validateSignalHeadReference(name); 361 if (name == null) { 362 return false; 363 } 364 break; 365 case SIGNALMAST: 366 name = _parent.validateSignalMastReference(name); 367 if (name == null) { 368 return false; 369 } 370 break; 371 case MEMORY: 372 name = _parent.validateMemoryReference(name); 373 if (name == null) { 374 return false; 375 } 376 break; 377 case LOGIX: 378 name = _parent.validateLogixReference(name); 379 if (name == null) { 380 return false; 381 } 382 break; 383 case WARRANT: 384 name = _parent.validateWarrantReference(name); 385 if (name == null) { 386 return false; 387 } 388 break; 389 case OBLOCK: 390 name = _parent.validateOBlockReference(name); 391 if (name == null) { 392 return false; 393 } 394 break; 395 case ENTRYEXIT: 396 name = _parent.validateEntryExitReference(name); 397 if (name == null) { 398 return false; 399 } 400 break; 401 default: 402 break; 403 } 404 return true; 405 } 406 407 //------------------- Table Models ---------------------- 408 409 abstract class CopyTableModel extends AbstractTableModel{ 410 411 boolean _isActionTable; 412 boolean _isComboTable; 413 414 CopyTableModel(boolean isAction, boolean isCombo) { 415 _isActionTable = isAction; 416 _isComboTable = isCombo; 417 log.debug("CopyTableModel: isAction= {}, _isComboTable= {}", isAction, _isComboTable); 418 } 419 420 boolean isActionTable() { 421 return _isActionTable; 422 } 423 424 boolean isComboTable() { 425 return _isComboTable; 426 } 427 } 428 429 class VariableCopyTableModel extends CopyTableModel{ 430 431 static final int ROWNUM_COLUMN = 0; 432 static final int NAME_COLUMN = 1; 433 static final int DESCRIPTION_COLUMN = 2; 434 435 VariableCopyTableModel(boolean isAction, boolean isCombo) { 436 super(isAction, isCombo); 437 } 438 439 @Override 440 public Class<?> getColumnClass(int c) { 441 switch (c) { 442 case NAME_COLUMN: 443 if (_isComboTable) { 444 return JComboBox.class; 445 } else { 446 return JTextField.class; 447 } 448 case DESCRIPTION_COLUMN: 449 return String.class; 450 default: 451 // fall through 452 break; 453 } 454 return String.class; 455 } 456 457 @Override 458 public int getColumnCount() { 459 return 3; 460 } 461 462 @Override 463 public boolean isCellEditable(int r, int col) { 464 if (col == NAME_COLUMN) { 465 return true; 466 } 467 return false; 468 } 469 470 @Override 471 public String getColumnName(int col) { 472 switch (col) { 473 case ROWNUM_COLUMN: 474 return (Bundle.getMessage("ColumnLabelRow")); // NOI18N 475 case NAME_COLUMN: 476 return (Bundle.getMessage("ColumnLabelName")); // NOI18N 477 case DESCRIPTION_COLUMN: 478 return (Bundle.getMessage("ColumnLabelDescription")); // NOI18N 479 default: 480 break; 481 } 482 return ""; 483 } 484 485 public int getPreferredWidth(int col) { 486 switch (col) { 487 case ROWNUM_COLUMN: 488 return 10; 489 case NAME_COLUMN: 490 return 200; 491 case DESCRIPTION_COLUMN: 492 return 600; 493 default: 494 break; 495 } 496 return 10; 497 } 498 499 @Override 500 public int getRowCount() { 501 return _variableList.size(); 502 } 503 504 @Override 505 public Object getValueAt(int row, int col) { 506 if (row >= getRowCount()) { 507 return null; 508 } 509 switch (col) { 510 case ROWNUM_COLUMN: 511 return ("R" + (row + 1)); // NOI18N 512 case NAME_COLUMN: 513 return _variableList.get(row).getName(); // NOI18N 514 case DESCRIPTION_COLUMN: 515 return _variableList.get(row).toString(); 516 default: 517 break; 518 } 519 return null; 520 } 521 522 @Override 523 public void setValueAt(Object value, int row, int col) { 524 if (row >= getRowCount()) { 525 return; 526 } 527 if (col == NAME_COLUMN) { 528 String name = (String)value; 529 ConditionalVariable variable = _variableList.get(row); 530 if (validateVariable(name, variable)) { 531 variable.setName(name); 532 this.fireTableRowsDeleted(row, row); 533 } 534 } 535 } 536 } 537 538 class ActionCopyTableModel extends CopyTableModel { 539 540 static final int NAME_COLUMN = 0; 541 static final int DESCRIPTION_COLUMN = 1; 542 static final int DELETE_COLUMN = 2; 543 544 boolean _isActionTable; 545 boolean _isComboTable; 546 547 ActionCopyTableModel(boolean isAction, boolean isCombo) { 548 super(isAction, isCombo); 549 } 550 551 @Override 552 public Class<?> getColumnClass(int c) { 553 switch (c) { 554 case NAME_COLUMN: 555 if (_isComboTable) { 556 return JComboBox.class; 557 } else { 558 return JTextField.class; 559 } 560 case DESCRIPTION_COLUMN: 561 return String.class; 562 case DELETE_COLUMN: 563 return JButton.class; 564 default: 565 // fall through 566 break; 567 } 568 return String.class; 569 } 570 571 @Override 572 public int getColumnCount() { 573 return 3; 574 } 575 576 @Override 577 public boolean isCellEditable(int r, int col) { 578 if (col == DESCRIPTION_COLUMN) { 579 return false; 580 } 581 return true; 582 } 583 584 @Override 585 public String getColumnName(int col) { 586 switch (col) { 587 case NAME_COLUMN: 588 return (Bundle.getMessage("ColumnLabelName")); // NOI18N 589 case DESCRIPTION_COLUMN: 590 return (Bundle.getMessage("ColumnLabelDescription")); // NOI18N 591 case DELETE_COLUMN: 592 return ""; 593 default: 594 break; 595 } 596 return ""; 597 } 598 599 public int getPreferredWidth(int col) { 600 switch (col) { 601 case NAME_COLUMN: 602 return 200; 603 case DESCRIPTION_COLUMN: 604 return 600; 605 default: 606 break; 607 } 608 return 10; 609 } 610 611 @Override 612 public int getRowCount() { 613 return _actionList.size(); 614 } 615 616 @Override 617 public Object getValueAt(int row, int col) { 618 if (row >= getRowCount()) { 619 return null; 620 } 621 ConditionalAction action = _actionList.get(row); 622 switch (col) { 623 case NAME_COLUMN: 624 return action.getDeviceName(); 625 case DESCRIPTION_COLUMN: 626 return action.description(_parent._curConditional.getTriggerOnChange()); 627 case DELETE_COLUMN: 628 return Bundle.getMessage("ButtonDelete"); // NOI18N 629 default: 630 break; 631 } 632 return null; 633 } 634 635 @Override 636 public void setValueAt(Object value, int row, int col) { 637 if (row >= getRowCount()) { 638 return; 639 } 640 if (col == NAME_COLUMN) { 641 ConditionalAction action = _actionList.get(row); 642 String name = (String)value; 643 if (validateAction(name, action)) { 644 action.setDeviceName(name); 645 this.fireTableRowsDeleted(row, row); 646 } 647 } else if (col == DELETE_COLUMN) { 648 _actionList.remove(row); 649 this.fireTableRowsDeleted(row, row); 650 } 651 } 652 } 653 654 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConditionalCopyFrame.class); 655}