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