001package jmri.jmrit.beantable; 002 003import java.awt.*; 004import java.awt.event.ActionEvent; 005import java.awt.event.ActionListener; 006import java.awt.event.KeyEvent; 007import java.io.File; 008import java.io.IOException; 009import java.util.ArrayList; 010import java.util.HashMap; 011import java.util.List; 012import java.util.SortedSet; 013import java.util.TreeSet; 014 015import javax.annotation.CheckForNull; 016import javax.annotation.Nonnull; 017 018import javax.swing.BorderFactory; 019import javax.swing.Box; 020import javax.swing.BoxLayout; 021import javax.swing.ButtonGroup; 022import javax.swing.DefaultCellEditor; 023import javax.swing.JButton; 024import javax.swing.JCheckBox; 025import javax.swing.JComboBox; 026import javax.swing.JComponent; 027import javax.swing.JDialog; 028import javax.swing.JFileChooser; 029import javax.swing.JLabel; 030import javax.swing.JMenu; 031import javax.swing.JMenuItem; 032import javax.swing.JPanel; 033import javax.swing.JRadioButtonMenuItem; 034import javax.swing.JScrollPane; 035import javax.swing.JTable; 036import javax.swing.JTextArea; 037import javax.swing.JTextField; 038import javax.swing.table.TableColumn; 039 040import jmri.*; 041import jmri.NamedBean.DisplayOptions; 042import jmri.jmrit.conditional.ConditionalEditBase; 043import jmri.jmrit.conditional.ConditionalListEdit; 044import jmri.jmrit.conditional.ConditionalTreeEdit; 045import jmri.jmrit.conditional.ConditionalListCopy; 046import jmri.jmrit.logixng.tools.ImportLogix; 047import jmri.jmrit.sensorgroup.SensorGroupFrame; 048import jmri.util.FileUtil; 049import jmri.util.JmriJFrame; 050import jmri.util.swing.JmriJOptionPane; 051 052/** 053 * Swing action to create and register a Logix Table. 054 * <p> 055 * Also contains the panes to create, edit, and delete a Logix. Conditional 056 * editing has been moved to ConditionalListView or CondtionalTreeView. 057 * <p> 058 * Most of the text used in this GUI is in BeanTableBundle.properties, accessed 059 * via Bundle.getMessage(). 060 * 201803 Moved all keys from LogixTableBundle.properties to 061 * BeanTableBundle.properties to simplify i18n. 062 * <p> 063 * Conditionals now have two policies to trigger execution of their action lists: 064 * <ol> 065 * <li>the previous policy - Trigger on change of state only 066 * <li>the new default - Trigger on any enabled state calculation 067 * </ol> 068 * Jan 15, 2011 - Pete Cressman 069 * <p> 070 * Two additional action and variable name selection methods have been added: 071 * <ol> 072 * <li>Single Pick List 073 * <li>Combo Box Selection 074 * </ol> 075 * The traditional tabbed Pick List with text entry is the default method. 076 * The Options menu has been expanded to list the 3 methods. 077 * Mar 27, 2017 - Dave Sand 078 * <p> 079 * Add a Browse Option to the Logix Select Menu This will display a window that 080 * creates a formatted list of the contents of the selected Logix with each 081 * Conditional, Variable and Action. The code is courtesy of Chuck Catania and 082 * is used with his permission. Apr 2, 2017 - Dave Sand 083 * 084 * @author Dave Duchamp Copyright (C) 2007 085 * @author Pete Cressman Copyright (C) 2009, 2010, 2020 086 * @author Matthew Harris copyright (c) 2009 087 * @author Dave Sand copyright (c) 2017 088 */ 089public class LogixTableAction extends AbstractTableAction<Logix> { 090 091 /** 092 * Create a LogixManager instance. 093 * 094 * @param s the Action title, not the title of the resulting frame. Perhaps 095 * this should be changed? 096 */ 097 public LogixTableAction(String s) { 098 super(s); 099 // set up managers - no need to use InstanceManager since both managers are 100 // Default only (internal). We use InstanceManager to get managers for 101 // compatibility with other facilities. 102 _logixManager = InstanceManager.getNullableDefault(LogixManager.class); 103 _conditionalManager = InstanceManager.getNullableDefault(ConditionalManager.class); 104 // disable ourself if there is no Logix manager or no Conditional manager available 105 if ((_logixManager == null) || (_conditionalManager == null)) { 106 setEnabled(false); 107 } 108 } 109 110 /** 111 * Create a LogixManager instance with default title. 112 */ 113 public LogixTableAction() { 114 this(Bundle.getMessage("TitleLogixTable")); 115 } 116 117 // ------------ Methods for Logix Table Window ------------ 118 119 /** 120 * Create the JTable DataModel, along with the changes (overrides of 121 * BeanTableDataModel) for the specific case of a Logix table. 122 */ 123 @Override 124 protected void createModel() { 125 m = new BeanTableDataModel<Logix>() { 126 // overlay the state column with the edit column 127 public static final int ENABLECOL = VALUECOL; 128 public static final int EDITCOL = DELETECOL; 129 protected String enabledString = Bundle.getMessage("ColumnHeadEnabled"); // NOI18N 130 131 @Override 132 public String getColumnName(int col) { 133 if (col == EDITCOL) { 134 return ""; // no heading on "Edit" 135 } 136 if (col == ENABLECOL) { 137 return enabledString; 138 } 139 return super.getColumnName(col); 140 } 141 142 @Override 143 public Class<?> getColumnClass(int col) { 144 if (col == EDITCOL) { 145 return String.class; 146 } 147 if (col == ENABLECOL) { 148 return Boolean.class; 149 } 150 return super.getColumnClass(col); 151 } 152 153 @Override 154 public int getPreferredWidth(int col) { 155 // override default value for SystemName and UserName columns 156 if (col == SYSNAMECOL) { 157 return new JTextField(12).getPreferredSize().width; 158 } 159 if (col == USERNAMECOL) { 160 return new JTextField(17).getPreferredSize().width; 161 } 162 if (col == EDITCOL) { 163 return new JTextField(12).getPreferredSize().width; 164 } 165 if (col == ENABLECOL) { 166 return new JTextField(5).getPreferredSize().width; 167 } 168 return super.getPreferredWidth(col); 169 } 170 171 @Override 172 public boolean isCellEditable(int row, int col) { 173 if (col == EDITCOL) { 174 return true; 175 } 176 if (col == ENABLECOL) { 177 return true; 178 } 179 return super.isCellEditable(row, col); 180 } 181 182 @Override 183 public Object getValueAt(int row, int col) { 184 switch (col) { 185 case EDITCOL: 186 return Bundle.getMessage("ButtonSelect"); // NOI18N 187 case ENABLECOL: 188 Logix logix = (Logix) getValueAt(row, SYSNAMECOL); 189 if (logix == null) { 190 return null; 191 } 192 return logix.getEnabled(); 193 default: 194 return super.getValueAt(row, col); 195 } 196 } 197 198 @Override 199 public void setValueAt(Object value, int row, int col) { 200 if (col == EDITCOL) { 201 // set up to edit 202 String sName = ((Logix) getValueAt(row, SYSNAMECOL)).getSystemName(); 203 if (Bundle.getMessage("ButtonEdit").equals(value)) { // NOI18N 204 editPressed(sName); 205 206 } else if (Bundle.getMessage("BrowserButton").equals(value)) { // NOI18N 207 browserPressed(sName); 208 209 } else if (Bundle.getMessage("ButtonCopy").equals(value)) { // NOI18N 210 copyPressed(sName); 211 212 } else if (Bundle.getMessage("ButtonDelete").equals(value)) { // NOI18N 213 deletePressed(sName); 214 } else if (Bundle.getMessage("ButtonExportLogixToLogixNG").equals(value)) { // NOI18N 215 exportToLogixNGPressed(sName); 216 } 217 } else if (col == ENABLECOL) { 218 // alternate 219 Logix x = (Logix) getValueAt(row, SYSNAMECOL); 220 boolean v = x.getEnabled(); 221 x.setEnabled(!v); 222 } else { 223 super.setValueAt(value, row, col); 224 } 225 } 226 227 /** 228 * Delete the bean after all the checking has been done. 229 * <p> 230 * Deactivates the Logix and remove its conditionals. 231 * 232 * @param bean of the Logix to delete 233 */ 234 @Override 235 protected void doDelete(Logix bean) { 236 bean.deActivateLogix(); 237 // delete the Logix and all its Conditionals 238 _logixManager.deleteLogix(bean); 239 } 240 241 @Override 242 protected boolean matchPropertyName(java.beans.PropertyChangeEvent e) { 243 if (e.getPropertyName().equals(enabledString)) { 244 return true; 245 } 246 return super.matchPropertyName(e); 247 } 248 249 @Override 250 public Manager<Logix> getManager() { 251 return InstanceManager.getDefault(LogixManager.class); 252 } 253 254 @Override 255 public Logix getBySystemName(@Nonnull String name) { 256 return InstanceManager.getDefault(LogixManager.class).getBySystemName( 257 name); 258 } 259 260 @Override 261 public Logix getByUserName(@Nonnull String name) { 262 return InstanceManager.getDefault(LogixManager.class).getByUserName( 263 name); 264 } 265 266 @Override 267 protected String getMasterClassName() { 268 return getClassName(); 269 } 270 271 @Override 272 public void configureTable(JTable table) { 273 table.setDefaultRenderer(Boolean.class, new EnablingCheckboxRenderer()); 274 table.setDefaultRenderer(JComboBox.class, new jmri.jmrit.symbolicprog.ValueRenderer()); 275 table.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor()); 276 super.configureTable(table); 277 } 278 279 /** 280 * Replace delete button with comboBox to edit/delete/copy/select 281 * Logix. 282 * 283 * @param table name of the Logix JTable holding the column 284 */ 285 @Override 286 protected void configDeleteColumn(JTable table) { 287 JComboBox<String> editCombo = new JComboBox<>(); 288 editCombo.addItem(Bundle.getMessage("ButtonSelect")); // NOI18N 289 editCombo.addItem(Bundle.getMessage("ButtonEdit")); // NOI18N 290 editCombo.addItem(Bundle.getMessage("BrowserButton")); // NOI18N 291 editCombo.addItem(Bundle.getMessage("ButtonCopy")); // NOI18N 292 editCombo.addItem(Bundle.getMessage("ButtonDelete")); // NOI18N 293 editCombo.addItem(Bundle.getMessage("ButtonExportLogixToLogixNG")); // NOI18N 294 TableColumn col = table.getColumnModel().getColumn(BeanTableDataModel.DELETECOL); 295 col.setCellEditor(new DefaultCellEditor(editCombo)); 296 } 297 298 // Not needed - here for interface compatibility 299 @Override 300 public void clickOn(Logix t) { 301 } 302 303 @Override 304 public String getValue(String s) { 305 return ""; 306 } 307 308 @Override 309 protected String getBeanType() { 310 return Bundle.getMessage("BeanNameLogix"); // NOI18N 311 } 312 }; 313 } 314 315 /** 316 * Set title of Logix table. 317 */ 318 @Override 319 protected void setTitle() { 320 f.setTitle(Bundle.getMessage("TitleLogixTable")); 321 } 322 323 /** 324 * Insert 2 table specific menus. 325 * <p> 326 * Accounts for the Window and Help menus, which are already added to the 327 * menu bar as part of the creation of the JFrame, by adding the new menus 2 328 * places earlier unless the table is part of the ListedTableFrame, which 329 * adds the Help menu later on. 330 * 331 * @param f the JFrame of this table 332 */ 333 @Override 334 public void setMenuBar(BeanTableFrame<Logix> f) { 335 loadSelectionMode(); 336 loadEditorMode(); 337 338 JMenu menu = new JMenu(Bundle.getMessage("MenuOptions")); // NOI18N 339 menu.setMnemonic(KeyEvent.VK_O); 340 javax.swing.JMenuBar menuBar = f.getJMenuBar(); 341 int pos = menuBar.getMenuCount() - 1; // count the number of menus to insert the TableMenus before 'Window' and 'Help' 342 int offset = 1; 343 log.debug("setMenuBar number of menu items = {}", pos); // NOI18N 344 for (int i = 0; i <= pos; i++) { 345 if (menuBar.getComponent(i) instanceof JMenu) { 346 if (((JMenu) menuBar.getComponent(i)).getText().equals(Bundle.getMessage("MenuHelp"))) { // NOI18N 347 offset = -1; // correct for use as part of ListedTableAction where the Help Menu is not yet present 348 } 349 } 350 } 351 352 ButtonGroup enableButtonGroup = new ButtonGroup(); 353 JRadioButtonMenuItem r = new JRadioButtonMenuItem(Bundle.getMessage("EnableAll")); // NOI18N 354 r.addActionListener( e -> enableAll(true)); 355 enableButtonGroup.add(r); 356 r.setSelected(true); 357 menu.add(r); 358 359 r = new JRadioButtonMenuItem(Bundle.getMessage("DisableAll")); // NOI18N 360 r.addActionListener( e -> enableAll(false)); 361 enableButtonGroup.add(r); 362 menu.add(r); 363 364 menu.addSeparator(); 365 366 ButtonGroup modeButtonGroup = new ButtonGroup(); 367 r = new JRadioButtonMenuItem(Bundle.getMessage("UseMultiPick")); // NOI18N 368 r.addItemListener( e -> setSelectionMode(SelectionMode.USEMULTI)); 369 modeButtonGroup.add(r); 370 menu.add(r); 371 r.setSelected(_selectionMode == SelectionMode.USEMULTI); 372 373 r = new JRadioButtonMenuItem(Bundle.getMessage("UseSinglePick")); // NOI18N 374 r.addItemListener( e -> setSelectionMode(SelectionMode.USESINGLE)); 375 modeButtonGroup.add(r); 376 menu.add(r); 377 r.setSelected(_selectionMode == SelectionMode.USESINGLE); 378 379 r = new JRadioButtonMenuItem(Bundle.getMessage("UseComboNameBoxes")); // NOI18N 380 r.addItemListener( e -> setSelectionMode(SelectionMode.USECOMBO)); 381 modeButtonGroup.add(r); 382 menu.add(r); 383 r.setSelected(_selectionMode == SelectionMode.USECOMBO); 384 385 menu.addSeparator(); 386 387 ButtonGroup viewButtonGroup = new ButtonGroup(); 388 r = new JRadioButtonMenuItem(Bundle.getMessage("ListEdit")); // NOI18N 389 r.addItemListener( e -> setEditorMode(EditMode.LISTEDIT)); 390 viewButtonGroup.add(r); 391 menu.add(r); 392 r.setSelected(_editMode == EditMode.LISTEDIT); 393 394 r = new JRadioButtonMenuItem(Bundle.getMessage("TreeEdit")); // NOI18N 395 r.addItemListener( e -> setEditorMode(EditMode.TREEEDIT)); 396 viewButtonGroup.add(r); 397 menu.add(r); 398 r.setSelected(_editMode == EditMode.TREEEDIT); 399 400 menuBar.add(menu, pos + offset); 401 402 menu = new JMenu(Bundle.getMessage("MenuTools")); // NOI18N 403 menu.setMnemonic(KeyEvent.VK_T); 404 405 JMenuItem item = new JMenuItem(Bundle.getMessage("OpenPickListTables")); // NOI18N 406 item.addActionListener( e -> openPickListTable()); 407 menu.add(item); 408 409 item = new JMenuItem(Bundle.getMessage("FindOrphans")); // NOI18N 410 item.addActionListener(this::findOrphansPressed); 411 menu.add(item); 412 413 item = new JMenuItem(Bundle.getMessage("EmptyConditionals")); // NOI18N 414 item.addActionListener(this::findEmptyPressed); 415 menu.add(item); 416 417 item = new JMenuItem(Bundle.getMessage("CrossReference")); // NOI18N 418 item.addActionListener(new ActionListener() { 419 BeanTableFrame<?> parent; 420 421 @Override 422 public void actionPerformed(ActionEvent e) { 423 new RefDialog(parent); 424 } 425 426 ActionListener init(BeanTableFrame<?> f) { 427 parent = f; 428 return this; 429 } 430 }.init(f)); 431 menu.add(item); 432 433 item = new JMenuItem(Bundle.getMessage("DisplayWhereUsed")); // NOI18N 434 item.addActionListener( e -> makeWhereUsedWindow()); 435 menu.add(item); 436 437 menuBar.add(menu, pos + offset + 1); // add this menu to the right of the previous 438 } 439 440 /** 441 * Get the saved mode selection, default to the tranditional tabbed pick 442 * list. 443 * <p> 444 * During the menu build process, the corresponding menu item is set to 445 * selected. 446 * 447 * @since 4.7.3 448 */ 449 void loadSelectionMode() { 450 Object modeName = InstanceManager.getDefault(UserPreferencesManager.class). 451 getProperty(getClassName(), "Selection Mode"); // NOI18N 452 if (modeName == null) { 453 _selectionMode = SelectionMode.USEMULTI; 454 } else { 455 String currentMode = (String) modeName; 456 switch (currentMode) { 457 case "USEMULTI": // NOI18N 458 _selectionMode = SelectionMode.USEMULTI; 459 break; 460 case "USESINGLE": // NOI18N 461 _selectionMode = SelectionMode.USESINGLE; 462 break; 463 case "USECOMBO": // NOI18N 464 _selectionMode = SelectionMode.USECOMBO; 465 break; 466 default: 467 log.warn("Invalid Logix conditional selection mode value, '{}', returned", currentMode); // NOI18N 468 _selectionMode = SelectionMode.USEMULTI; 469 } 470 } 471 } 472 473 /** 474 * Save the mode selection. Called by menu item change events. 475 * 476 * @since 4.7.3 477 * @param newMode The SelectionMode enum constant 478 */ 479 void setSelectionMode(SelectionMode newMode) { 480 _selectionMode = newMode; 481 InstanceManager.getDefault(UserPreferencesManager.class). 482 setProperty(getClassName(), "Selection Mode", newMode.toString()); // NOI18N 483 } 484 485 /** 486 * Get the saved mode selection, default to the traditional conditional 487 * list editor. 488 * <p> 489 * During the menu build process, the corresponding menu item is set to 490 * selected. 491 * 492 * @since 4.9.x 493 */ 494 void loadEditorMode() { 495 Object modeName = InstanceManager.getDefault(UserPreferencesManager.class). 496 getProperty(getClassName(), "Edit Mode"); // NOI18N 497 if (modeName == null) { 498 _editMode = EditMode.LISTEDIT; 499 } else { 500 String currentMode = (String) modeName; 501 switch (currentMode) { 502 case "LISTEDIT": // NOI18N 503 _editMode = EditMode.LISTEDIT; 504 break; 505 case "TREEEDIT": // NOI18N 506 _editMode = EditMode.TREEEDIT; 507 break; 508 default: 509 log.warn("Invalid conditional edit mode value, '{}', returned", currentMode); // NOI18N 510 _editMode = EditMode.LISTEDIT; 511 } 512 } 513 } 514 515 /** 516 * Save the view mode selection. Called by menu item change events. 517 * 518 * @since 4.9.x 519 * @param newMode The ViewMode enum constant 520 */ 521 void setEditorMode(EditMode newMode) { 522 _editMode = newMode; 523 InstanceManager.getDefault(UserPreferencesManager.class). 524 setProperty(getClassName(), "Edit Mode", newMode.toString()); // NOI18N 525 } 526 527 /** 528 * Open a new Pick List to drag Actions from to form Logix Conditionals. 529 */ 530 void openPickListTable() { 531 if (_pickTables == null) { 532 _pickTables = new jmri.jmrit.picker.PickFrame(Bundle.getMessage("TitlePickList")); // NOI18N 533 } else { 534 _pickTables.setVisible(true); 535 } 536 _pickTables.toFront(); 537 } 538 539 /** 540 * Find empty Conditional entries, called from menu. 541 * 542 * @see Maintenance#findEmptyPressed(java.awt.Frame) 543 * @param e the event heard 544 */ 545 void findEmptyPressed(ActionEvent e) { 546 Maintenance.findEmptyPressed(f); 547 } 548 549 /** 550 * Find orphaned entries, called from menu. 551 * 552 * @see Maintenance#findOrphansPressed(java.awt.Frame) 553 * @param e the event heard 554 */ 555 void findOrphansPressed(ActionEvent e) { 556 Maintenance.findOrphansPressed(f); 557 } 558 559 private class RefDialog extends JDialog { 560 561 JTextField _devNameField; 562 java.awt.Frame _parent; 563 564 RefDialog(java.awt.Frame frame) { 565 super(frame, Bundle.getMessage("CrossReference"), true); // NOI18N 566 _parent = frame; 567 JPanel extraPanel = new JPanel(); 568 extraPanel.setLayout(new BoxLayout(extraPanel, BoxLayout.Y_AXIS)); 569 _devNameField = new JTextField(30); 570 JPanel panel = makeEditPanel(_devNameField, "ElementName", "ElementNameHint"); // NOI18N 571 JButton referenceButton = new JButton(Bundle.getMessage("ReferenceButton")); // NOI18N 572 panel.add(referenceButton); 573 referenceButton.addActionListener(this::deviceReportPressed); 574 panel.add(referenceButton); 575 extraPanel.add(panel); 576 setContentPane(extraPanel); 577 pack(); 578 // setLocationRelativeTo((java.awt.Component)_pos); 579 setVisible(true); 580 } 581 582 void deviceReportPressed(ActionEvent e) { 583 Maintenance.deviceReportPressed(_devNameField.getText(), _parent); 584 dispose(); 585 } 586 } 587 588 void enableAll(boolean enable) { 589 for (Logix x : _logixManager.getNamedBeanSet()) { 590 x.setEnabled(enable); 591 } 592 } 593 594 @Override 595 protected String helpTarget() { 596 return "package.jmri.jmrit.beantable.LogixTable"; // NOI18N 597 } 598 599 // ------------ variable definitions ------------ 600 601 // Multi use variables 602 private ConditionalManager _conditionalManager = null; // set when LogixAction is created 603 private LogixManager _logixManager = null; // set when LogixAction is created 604 605 private ConditionalEditBase _baseEdit; 606 607 private boolean _showReminder = false; 608 private final boolean _checkEnabled = InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).isStoreCheckEnabled(); 609 private jmri.jmrit.picker.PickFrame _pickTables; 610 611 // Current focus variables 612 private Logix _curLogix = null; 613 614 // Add Logix Variables 615 private JmriJFrame addLogixFrame = null; 616 private final JTextField _systemName = new JTextField(20); 617 private final JTextField _addUserName = new JTextField(20); 618 private final JComboBox<String> _copyCombo = new JComboBox<>(); 619 620 private final JCheckBox _autoSystemName = new JCheckBox(Bundle.getMessage("LabelAutoSysName")); // NOI18N 621 private final JLabel _sysNameLabel = new JLabel(Bundle.getMessage("BeanNameLogix") 622 + " " + Bundle.getMessage("ColumnSystemName") + ":"); // NOI18N 623 private final JLabel _userNameLabel = new JLabel(Bundle.getMessage("BeanNameLogix") 624 + " " + Bundle.getMessage("ColumnUserName") + ":"); // NOI18N 625 private final String systemNameAuto = this.getClass().getName() + ".AutoSystemName"; // NOI18N 626 private JButton create; 627 628 // Edit Logix Variables 629 private boolean _inEditMode = false; 630 private boolean _inCopyMode = false; 631 private boolean _inAddMode = false; 632 633 /** 634 * Input selection names. 635 * 636 * @since 4.7.3 637 */ 638 public enum SelectionMode { 639 /** 640 * Use the traditional text field, with the tabbed Pick List available 641 * for drag-n-drop 642 */ 643 USEMULTI, 644 /** 645 * Use the traditional text field, but with a single Pick List that 646 * responds with a click 647 */ 648 USESINGLE, 649 /** 650 * Use combo boxes to select names instead of a text field. 651 */ 652 USECOMBO; 653 } 654 private SelectionMode _selectionMode; 655 656 /** 657 * Conditional edit view mode 658 * 659 * @since 4.9.x 660 */ 661 public enum EditMode { 662 /** 663 * Use the traditional table list mode for editing conditionals 664 */ 665 LISTEDIT, 666 /** 667 * Use the tree based mode for editing condtiionals 668 */ 669 TREEEDIT; 670 } 671 private EditMode _editMode; 672 673 // Save conditional reference target names before updating 674 private final TreeSet<String> _saveTargetNames = new TreeSet<>(); 675 private final HashMap<String, ArrayList<String>> _saveTargetList = new HashMap<>(); 676 677 // ------------ Methods for Add Logix Window ------------ 678 679 /** 680 * Respond to the Add button in Logix table Creates and/or initialize the 681 * Add Logix pane. 682 * 683 * @param e The event heard 684 */ 685 @Override 686 protected void addPressed(ActionEvent e) { 687 // possible change 688 if (!checkFlags(null)) { 689 return; 690 } 691 _showReminder = true; 692 // make an Add Logix Frame 693 if (addLogixFrame == null) { 694 JPanel panel5 = makeAddLogixFrame("TitleAddLogix", "AddLogixMessage", 695 "package.jmri.jmrit.beantable.LogixAddEdit"); // NOI18N 696 // Create Logix 697 create = new JButton(Bundle.getMessage("ButtonCreate")); // NOI18N 698 panel5.add(create); 699 create.addActionListener(this::createPressed); 700 create.setToolTipText(Bundle.getMessage("LogixCreateButtonHint")); // NOI18N 701 } 702 _inAddMode = true; 703 addLogixFrame.setEscapeKeyClosesWindow(true); 704 addLogixFrame.getRootPane().setDefaultButton(create); 705 addLogixFrame.pack(); 706 addLogixFrame.setVisible(true); 707 _autoSystemName.setSelected(false); 708 addLogixFrame.setLocationRelativeTo(getFrame()); 709 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent( prefMgr -> 710 _autoSystemName.setSelected(prefMgr.getSimplePreferenceState(systemNameAuto))); 711 } 712 713 /** 714 * Create or copy Logix frame. 715 * 716 * @param titleId property key to fetch as title of the frame (using Bundle) 717 * @param messageId part 1 of property key to fetch as user instruction on 718 * pane, either 1 or 2 is added to form the whole key 719 * @param helpFile help file name 720 * @return the button JPanel 721 */ 722 JPanel makeAddLogixFrame(String titleId, String messageId, String helpFile) { 723 addLogixFrame = new JmriJFrame(Bundle.getMessage(titleId)); 724 addLogixFrame.addHelpMenu(helpFile, true); // NOI18N 725 addLogixFrame.setLocation(50, 30); 726 Container contentPane = addLogixFrame.getContentPane(); 727 contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); 728 JPanel p; 729 p = new JPanel(); 730 p.setLayout(new FlowLayout()); 731 p.setLayout(new java.awt.GridBagLayout()); 732 java.awt.GridBagConstraints c = new java.awt.GridBagConstraints(); 733 c.gridwidth = 1; 734 c.gridheight = 1; 735 c.gridx = 0; 736 c.gridy = 0; 737 c.anchor = java.awt.GridBagConstraints.EAST; 738 p.add(_sysNameLabel, c); 739 c.gridy = 1; 740 p.add(_userNameLabel, c); 741 c.gridx = 1; 742 c.gridy = 0; 743 c.anchor = java.awt.GridBagConstraints.WEST; 744 c.weightx = 1.0; 745 c.fill = java.awt.GridBagConstraints.HORIZONTAL; // text field will expand 746 if ("TitleCopyLogix".equals(titleId)) { 747 p.add(_copyCombo, c); 748 } else { 749 p.add(_systemName, c); 750 } 751 c.gridy = 1; 752 p.add(_addUserName, c); 753 c.gridx = 2; 754 c.gridy = 1; 755 c.anchor = java.awt.GridBagConstraints.WEST; 756 c.weightx = 1.0; 757 c.fill = java.awt.GridBagConstraints.HORIZONTAL; // text field will expand 758 c.gridy = 0; 759 p.add(_autoSystemName, c); 760 _addUserName.setToolTipText(Bundle.getMessage("LogixUserNameHint")); // NOI18N 761 _systemName.setToolTipText(Bundle.getMessage("LogixSystemNameHint")); // NOI18N 762 contentPane.add(p); 763 // set up message 764 JPanel panel3 = new JPanel(); 765 panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS)); 766 JPanel panel31 = new JPanel(); 767 panel31.setLayout(new FlowLayout()); 768 JLabel message1 = new JLabel(Bundle.getMessage(messageId + "1")); // NOI18N 769 panel31.add(message1); 770 JPanel panel32 = new JPanel(); 771 JLabel message2 = new JLabel(Bundle.getMessage(messageId + "2")); // NOI18N 772 panel32.add(message2); 773 panel3.add(panel31); 774 panel3.add(panel32); 775 contentPane.add(panel3); 776 777 // set up create and cancel buttons 778 JPanel panel5 = new JPanel(); 779 panel5.setLayout(new FlowLayout()); 780 // Cancel 781 JButton cancel = new JButton(Bundle.getMessage("ButtonCancel")); // NOI18N 782 panel5.add(cancel); 783 cancel.addActionListener(this::cancelAddPressed); 784 cancel.setToolTipText(Bundle.getMessage("CancelLogixButtonHint")); // NOI18N 785 786 addLogixFrame.addWindowListener(new java.awt.event.WindowAdapter() { 787 @Override 788 public void windowClosing(java.awt.event.WindowEvent e) { 789 cancelAddPressed(null); 790 } 791 }); 792 contentPane.add(panel5); 793 794 _autoSystemName.addItemListener( e -> autoSystemName()); 795 return panel5; 796 } 797 798 /** 799 * Enable/disable fields for data entry when user selects to have system 800 * name automatically generated. 801 */ 802 void autoSystemName() { 803 if (_autoSystemName.isSelected()) { 804 _systemName.setEnabled(false); 805 _sysNameLabel.setEnabled(false); 806 } else { 807 _systemName.setEnabled(true); 808 _sysNameLabel.setEnabled(true); 809 } 810 } 811 812 /** 813 * Respond to the Cancel button in Add Logix window. 814 * <p> 815 * Note: Also get there if the user closes the Add Logix window. 816 * 817 * @param e The event heard 818 */ 819 void cancelAddPressed(ActionEvent e) { 820 addLogixFrame.setVisible(false); 821 addLogixFrame.dispose(); 822 addLogixFrame = null; 823 _inAddMode = false; 824 _inCopyMode = false; 825 if (f != null) { 826 f.setVisible(true); 827 } 828 } 829 830 /** 831 * Respond to the Copy Logix button in Add Logix window. 832 * <p> 833 * Provides a pane to set new properties of the copy. 834 * 835 * @param sName system name of Logix to be copied 836 */ 837 void copyPressed(String sName) { 838 if (!checkFlags(sName)) { 839 return; 840 } 841 _showReminder = true; 842 843 // Refresh combo box Logix list 844 _copyCombo.removeActionListener(this::copyComboListener); 845 _copyCombo.removeAllItems(); 846 _copyCombo.addItem(""); 847 var logixList = InstanceManager.getDefault(LogixManager.class).getNamedBeanSet(); 848 logixList.forEach( lgx -> _copyCombo.addItem(lgx.getSystemName())); 849 _copyCombo.setEditable(true); 850 _copyCombo.setSelectedIndex(0); 851 _copyCombo.addActionListener(this::copyComboListener); 852 jmri.util.swing.JComboBoxUtil.setupComboBoxMaxRows(_copyCombo); 853 854 // make an Add Logix Frame 855 if (addLogixFrame == null) { 856 JPanel panel5 = makeAddLogixFrame("TitleCopyLogix", "CopyLogixMessage", 857 "package.jmri.jmrit.conditional.ConditionalCopy"); // NOI18N 858 // Create Logix 859 JButton copyButton = new JButton(Bundle.getMessage("ButtonCopy")); // NOI18N 860 panel5.add(copyButton); 861 copyButton.addActionListener(new CopyAction(sName)); 862 addLogixFrame.pack(); 863 addLogixFrame.setVisible(true); 864 _autoSystemName.setSelected(false); 865 addLogixFrame.setLocationRelativeTo(getFrame()); 866 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent( prefMgr -> 867 _autoSystemName.setSelected(prefMgr.getSimplePreferenceState(systemNameAuto))); 868 _inCopyMode = true; 869 } 870 _curLogix = _logixManager.getBySystemName(sName); 871 } 872 873 private class CopyAction implements ActionListener { 874 String _lgxName; 875 CopyAction(String lgxName) { 876 _lgxName = lgxName; 877 } 878 @Override 879 public void actionPerformed(ActionEvent e) { 880 copyLogixPressed(_lgxName); 881 } 882 883 /** 884 * Copy the Logix as configured in the Copy set up pane. 885 * 886 * @param lgxName Logix system name to be copied 887 */ 888 private void copyLogixPressed(String lgxName) { 889 _systemName.setText((String) _copyCombo.getSelectedItem()); 890 String uName = _addUserName.getText(); 891 if (uName.length() == 0) { 892 uName = null; 893 } 894 Logix targetLogix; 895 if (_autoSystemName.isSelected()) { 896 if (!checkLogixUserName(uName)) { 897 return; 898 } 899 targetLogix = _logixManager.createNewLogix(uName); 900 } else { 901 // Validate the system name 902 if (!checkLogixSysName()) { 903 cancelAddPressed(null); 904 return; 905 } 906 var sName = _systemName.getText(); // Use the validated, possibly changed, system name 907 908 targetLogix = _logixManager.getBySystemName(sName); 909 if (targetLogix == null && uName != null) { 910 targetLogix = _logixManager.getByUserName(uName); 911 } 912 if (targetLogix != null) { 913 int result = JmriJOptionPane.showConfirmDialog(f, 914 Bundle.getMessage("ConfirmLogixDuplicate", 915 targetLogix.getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME), lgxName), // NOI18N 916 Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION, // NOI18N 917 JmriJOptionPane.QUESTION_MESSAGE); 918 if (JmriJOptionPane.YES_OPTION != result) { 919 return; 920 } 921 } 922 if (targetLogix == null) { 923 targetLogix = _logixManager.createNewLogix(sName, uName); 924 if (targetLogix == null) { 925 // should never get here unless there is an assignment conflict 926 log.error("Failure to create Logix with System Name: {}", sName); // NOI18N 927 return; 928 } 929 } else { 930 targetLogix.setUserName(uName); 931 } 932 } 933 cancelAddPressed(null); 934 _baseEdit = new ConditionalListCopy(lgxName, targetLogix); 935 _baseEdit.locateAt(getFrame()); 936 _inCopyMode = true; 937 _baseEdit.addLogixEventListener(new ConditionalBaseListener(lgxName)); 938 } 939 940 } 941 942 /** 943 * Set the user name input field. 944 * @param e The action event. 945 */ 946 private void copyComboListener(ActionEvent e) { 947 if ( ! "comboBoxChanged".equals(e.getActionCommand())) { 948 return; 949 } 950 951 var name = ""; 952 var index = _copyCombo.getSelectedIndex(); 953 if (index > 0) { 954 var logix = _logixManager.getLogix(_copyCombo.getItemAt(index)); 955 if (logix != null) { 956 var userName = logix.getUserName(); 957 if (userName != null) { 958 name = userName; 959 } 960 } 961 } 962 _addUserName.setText(name); 963 } 964 965 /** 966 * Check and warn if a string is already in use as the user name of a Logix. 967 * 968 * @param uName the suggested name 969 * @return true if not in use 970 */ 971 boolean checkLogixUserName(String uName) { 972 // check if a Logix with the same user name exists 973 if (uName != null && uName.trim().length() > 0) { 974 Logix x = _logixManager.getByUserName(uName); 975 if (x != null) { 976 // Logix with this user name already exists 977 JmriJOptionPane.showMessageDialog(getFrame(), 978 Bundle.getMessage("LogixError3"), Bundle.getMessage("ErrorTitle"), 979 JmriJOptionPane.ERROR_MESSAGE); 980 return false; 981 } 982 } 983 return true; 984 } 985 986 /** 987 * Check for a valid Logix system name. 988 * A valid name starts with the Logix prefix consisting of the Internal system prefix (normally I) + X, 989 * and at least 1 additional character. The prefix will be added if necessary. 990 * Any makeSystemName errors are logged to the system console and a dialog is displayed. 991 * @return true if the name is now valid. 992 */ 993 boolean checkLogixSysName() { 994 String sName = _systemName.getText(); 995 996 try { 997 sName = InstanceManager.getDefault(LogixManager.class).makeSystemName(sName); 998 } catch (NamedBean.BadSystemNameException ex) { 999 JmriJOptionPane.showMessageDialog(getFrame(), 1000 Bundle.getMessage("LogixError8"), Bundle.getMessage("ErrorTitle"), 1001 JmriJOptionPane.ERROR_MESSAGE); 1002 return false; 1003 } 1004 _systemName.setText(sName); 1005 return true; 1006 } 1007 1008 /** 1009 * Check if another Logix editing session is currently open or no system 1010 * name is provided. 1011 * 1012 * @param sName system name of Logix to be copied 1013 * @return true if a new session may be started 1014 */ 1015 boolean checkFlags(String sName) { 1016 if (_inEditMode) { 1017 // Already editing a Logix, ask for completion of that edit 1018 JmriJOptionPane.showMessageDialog(getFrame(), 1019 Bundle.getMessage("LogixError32", _curLogix.getSystemName()), 1020 Bundle.getMessage("ErrorTitle"), 1021 JmriJOptionPane.ERROR_MESSAGE); 1022 _baseEdit.bringToFront(); 1023 return false; 1024 } 1025 1026 if (_inAddMode) { 1027 // Adding a Logix, ask for completion of that edit 1028 JmriJOptionPane.showMessageDialog(getFrame(), 1029 Bundle.getMessage("LogixError33"), 1030 Bundle.getMessage("ErrorTitle"), // NOI18N 1031 JmriJOptionPane.ERROR_MESSAGE); 1032 addLogixFrame.toFront(); 1033 return false; 1034 } 1035 1036 if (_inCopyMode) { 1037 // Already copying a Logix, ask for completion of that edit 1038 JmriJOptionPane.showMessageDialog(getFrame(), 1039 Bundle.getMessage("LogixError31", _curLogix.getSystemName()), 1040 Bundle.getMessage("ErrorTitle"), // NOI18N 1041 JmriJOptionPane.ERROR_MESSAGE); 1042 _baseEdit.bringToFront(); 1043 return false; 1044 } 1045 1046 if (sName != null) { 1047 // check if a Logix with this name exists 1048 Logix x = _logixManager.getBySystemName(sName); 1049 if (x == null) { 1050 // Logix does not exist, so cannot be edited 1051 log.error("No Logix with system name: {}", sName); 1052 JmriJOptionPane.showMessageDialog(getFrame(), 1053 Bundle.getMessage("LogixError5"), 1054 Bundle.getMessage("ErrorTitle"), // NOI18N 1055 JmriJOptionPane.ERROR_MESSAGE); 1056 return false; 1057 } 1058 } 1059 return true; 1060 } 1061 1062 /** 1063 * Respond to the Create Logix button in Add Logix window. 1064 * 1065 * @param e The event heard 1066 */ 1067 void createPressed(ActionEvent e) { 1068 // possible change 1069 _showReminder = true; 1070 String sName; 1071 String uName = _addUserName.getText(); 1072 if (uName.length() == 0) { 1073 uName = null; 1074 } 1075 if (_autoSystemName.isSelected()) { 1076 if (!checkLogixUserName(uName)) { 1077 return; 1078 } 1079 _curLogix = _logixManager.createNewLogix(uName); 1080 sName = _curLogix.getSystemName(); 1081 } else { 1082 if (!checkLogixSysName()) { 1083 return; 1084 } 1085 // Get validated system name 1086 sName = _systemName.getText(); 1087 // check if a Logix with this name already exists 1088 Logix x; 1089 try { 1090 x = _logixManager.getBySystemName(sName); 1091 } catch (Exception ex) { 1092 // user input no good 1093 handleCreateException(sName); 1094 return; // without creating 1095 } 1096 if (x != null) { 1097 // Logix already exists 1098 JmriJOptionPane.showMessageDialog(getFrame(), Bundle.getMessage("LogixError1"), 1099 Bundle.getMessage("ErrorTitle"), // NOI18N 1100 JmriJOptionPane.ERROR_MESSAGE); 1101 return; 1102 } 1103 if (!checkLogixUserName(uName)) { 1104 return; 1105 } 1106 // Create the new Logix 1107 _curLogix = _logixManager.createNewLogix(sName, uName); 1108 if (_curLogix == null) { 1109 // should never get here unless there is an assignment conflict 1110 log.error("Failure to create Logix with System Name: {}", sName); // NOI18N 1111 return; 1112 } 1113 } 1114 cancelAddPressed(null); 1115 // create the Edit Logix Window 1116 editPressed(sName); 1117 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent( prefMgr -> 1118 prefMgr.setSimplePreferenceState(systemNameAuto, _autoSystemName.isSelected())); 1119 } 1120 1121 void handleCreateException(String sysName) { 1122 JmriJOptionPane.showMessageDialog(getFrame(), 1123 Bundle.getMessage("ErrorLogixAddFailed", sysName), // NOI18N 1124 Bundle.getMessage("ErrorTitle"), // NOI18N 1125 JmriJOptionPane.ERROR_MESSAGE); 1126 } 1127 1128 // ------------ Methods for Edit Logix Pane ------------ 1129 1130 /** 1131 * Respond to the Edit button pressed in Logix table. 1132 * 1133 * @param sName system name of Logix to be edited 1134 */ 1135 void editPressed(String sName) { 1136 if (!checkFlags(sName)) { 1137 return; 1138 } 1139 1140 if (sName.equals(SensorGroupFrame.logixSysName)) { 1141 // Sensor group message 1142 JmriJOptionPane.showMessageDialog(getFrame(), 1143 Bundle.getMessage("LogixWarn8", SensorGroupFrame.logixUserName, SensorGroupFrame.logixSysName), 1144 Bundle.getMessage("WarningTitle"), // NOI18N 1145 JmriJOptionPane.WARNING_MESSAGE); 1146 return; 1147 } 1148 _curLogix = _logixManager.getBySystemName(sName); 1149 1150 // Create a new conditional edit view, add the listener. 1151 if (_editMode == EditMode.TREEEDIT) { 1152 _baseEdit = new ConditionalTreeEdit(sName); 1153 } else { 1154 _baseEdit = new ConditionalListEdit(sName); 1155 } 1156 _baseEdit.locateAt(getFrame()); 1157 _inEditMode = true; 1158 _baseEdit.addLogixEventListener(new ConditionalBaseListener(sName)); 1159 } 1160 1161 private class ConditionalBaseListener implements ConditionalEditBase.LogixEventListener { 1162 String _lgxName; 1163 ConditionalBaseListener(String lgxName) { 1164 _lgxName = lgxName; 1165 } 1166 1167 @Override 1168 public void logixEventOccurred() { 1169 _baseEdit.logixData.forEach((key, value) -> { 1170 if ("Finish".equals(key)) { // NOI18N 1171 _baseEdit = null; 1172 _inEditMode = false; 1173 _inCopyMode = false; 1174 Logix x = _logixManager.getBySystemName(value); 1175 if (x == null) { 1176 log.error("Found no logix for name {} when done", value); 1177 return; 1178 } 1179 x.activateLogix(); 1180 f.setVisible(true); 1181 } else if ("Delete".equals(key)) { // NOI18N 1182 deletePressed(value); 1183 } else if ("chgUname".equals(key)) { // NOI18N 1184 Logix x = _logixManager.getBySystemName(_lgxName); 1185 if (x == null) { 1186 log.error("Found no logix for name {} when changing user name (2)", _lgxName); 1187 return; 1188 } 1189 x.setUserName(value); 1190 m.fireTableDataChanged(); 1191 } 1192 }); 1193 } 1194 } 1195 1196 /** 1197 * Display reminder to save. 1198 */ 1199 void showSaveReminder() { 1200 if (_showReminder && !_checkEnabled) { 1201 if (InstanceManager.getNullableDefault(UserPreferencesManager.class) != null) { 1202 InstanceManager.getDefault(UserPreferencesManager.class). 1203 showInfoMessage(Bundle.getMessage("ReminderTitle"), 1204 Bundle.getMessage("ReminderSaveString", Bundle.getMessage("MenuItemLogixTable")), // NOI18N 1205 getClassName(), 1206 "remindSaveLogix"); // NOI18N 1207 } 1208 } 1209 } 1210 1211 @Override 1212 public void setMessagePreferencesDetails() { 1213 HashMap<Integer, String> options = new HashMap<>(3); 1214 options.put(0x00, Bundle.getMessage("DeleteAsk")); // NOI18N 1215 options.put(0x01, Bundle.getMessage("DeleteNever")); // NOI18N 1216 options.put(0x02, Bundle.getMessage("DeleteAlways")); // NOI18N 1217 InstanceManager.getDefault(UserPreferencesManager.class).setMessageItemDetails(getClassName(), 1218 "delete", Bundle.getMessage("DeleteLogix"), options, 0x00); // NOI18N 1219 InstanceManager.getDefault(UserPreferencesManager.class).setPreferenceItemDetails(getClassName(), 1220 "remindSaveLogix", Bundle.getMessage("HideSaveReminder")); // NOI18N 1221 super.setMessagePreferencesDetails(); 1222 } 1223 1224 /** 1225 * Respond to the Delete combo selection Logix window or conditional view 1226 * delete request. 1227 * 1228 * @param sName system name of bean to be deleted 1229 */ 1230 void deletePressed(String sName) { 1231 if (!checkConditionalReferences(sName)) { 1232 return; 1233 } 1234 final Logix x = _logixManager.getBySystemName(sName); 1235 final UserPreferencesManager p; 1236 p = InstanceManager.getNullableDefault(UserPreferencesManager.class); 1237 if (p != null && p.getMultipleChoiceOption(getClassName(), "delete") == 0x02) { // NOI18N 1238 if (x != null) { 1239 _logixManager.deleteLogix(x); 1240 deleteSourceWhereUsed(); 1241 } 1242 } else { 1243 final JDialog dialog = new JDialog(); 1244 String msg; 1245 dialog.setTitle(Bundle.getMessage("QuestionTitle")); // NOI18N 1246 dialog.setLocationRelativeTo(getFrame()); 1247 dialog.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); 1248 JPanel container = new JPanel(); 1249 container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 1250 container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); 1251 msg = Bundle.getMessage("ConfirmLogixDelete", sName); // NOI18N 1252 JLabel question = new JLabel(msg); 1253 question.setAlignmentX(Component.CENTER_ALIGNMENT); 1254 container.add(question); 1255 1256 final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting")); // NOI18N 1257 remember.setFont(remember.getFont().deriveFont(10f)); 1258 remember.setAlignmentX(Component.CENTER_ALIGNMENT); 1259 1260 JButton yesButton = new JButton(Bundle.getMessage("ButtonYes")); // NOI18N 1261 JButton noButton = new JButton(Bundle.getMessage("ButtonNo")); // NOI18N 1262 JPanel button = new JPanel(); 1263 button.setAlignmentX(Component.CENTER_ALIGNMENT); 1264 button.add(yesButton); 1265 button.add(noButton); 1266 container.add(button); 1267 1268 // there is no point in remebering this the user will never be 1269 // able to delete a bean! 1270 noButton.addActionListener( e -> /** if(remember.isSelected()){ setDisplayDeleteMsg(0x01); }*/ 1271 dialog.dispose()); 1272 1273 yesButton.addActionListener( e -> { 1274 if (p != null && remember.isSelected()) { 1275 p.setMultipleChoiceOption(getClassName(), "delete", 0x02); // NOI18N 1276 } 1277 if (x != null) { 1278 _logixManager.deleteLogix(x); 1279 deleteSourceWhereUsed(); 1280 } 1281 dialog.dispose(); 1282 }); 1283 container.add(remember); 1284 container.setAlignmentX(Component.CENTER_ALIGNMENT); 1285 container.setAlignmentY(Component.CENTER_ALIGNMENT); 1286 dialog.getContentPane().add(container); 1287 dialog.pack(); 1288 dialog.setModal(true); 1289 dialog.setVisible(true); 1290 } 1291 1292 f.setVisible(true); 1293 } 1294 1295 /** 1296 * Respond to the Export to LogixNG combo selection Logix window request. 1297 * 1298 * @param sName system name of bean to export 1299 */ 1300 void exportToLogixNGPressed(String sName) { 1301 if (!checkConditionalReferences(sName)) { 1302 return; 1303 } 1304 final Logix logix = _logixManager.getBySystemName(sName); 1305 if (logix == null) { 1306 throw new NullPointerException("logix is null"); 1307 } 1308 1309 boolean error = false; 1310 StringBuilder errorMessage = new StringBuilder("<html><table border=\"1\" cellspacing=\"0\" cellpadding=\"2\">"); 1311 errorMessage.append("<tr><th>"); 1312 errorMessage.append(Bundle.getMessage("ColumnSystemName")); 1313 errorMessage.append("</th><th>"); 1314 errorMessage.append(Bundle.getMessage("ColumnUserName")); 1315 errorMessage.append("</th><th>"); 1316 errorMessage.append(Bundle.getMessage("ExportLogixColumnError")); 1317 errorMessage.append("</th></tr>"); 1318 1319 try { 1320 ImportLogix importLogix = new ImportLogix(logix, true, true); 1321 importLogix.doImport(); 1322 } catch (JmriException e) { 1323 errorMessage.append("<tr><td>"); 1324 errorMessage.append(logix.getSystemName()); 1325 errorMessage.append("</td><td>"); 1326 errorMessage.append(logix.getUserName() != null ? logix.getUserName() : ""); 1327 errorMessage.append("</td><td>"); 1328 errorMessage.append(e.getMessage()); 1329 errorMessage.append("</td></tr>"); 1330 log.error("Error thrown: {}", e, e); 1331 error = true; 1332 } 1333 1334 if (!error) { 1335 try { 1336 ImportLogix importLogix = new ImportLogix(logix, true, false); 1337 importLogix.doImport(); 1338 JmriJOptionPane.showMessageDialog(f, Bundle.getMessage("LogixIsExported", 1339 logix.getDisplayName()), Bundle.getMessage("TitleLogixExportSuccess"), 1340 JmriJOptionPane.INFORMATION_MESSAGE); 1341 } catch (JmriException e) { 1342 throw new RuntimeException("Unexpected error: "+e.getMessage(), e); 1343 } 1344 } else { 1345 errorMessage.append("</table></html>"); 1346 JmriJOptionPane.showMessageDialog(f, errorMessage.toString(), 1347 Bundle.getMessage("TitleLogixExportError"), JmriJOptionPane.ERROR_MESSAGE); 1348 } 1349 } 1350 1351 /** 1352 * Build a tree set from conditional references. 1353 * 1354 * @since 4.7.4 1355 * @param varList The ConditionalVariable list that might contain 1356 * conditional references 1357 * @param treeSet A tree set to be built from the varList data 1358 */ 1359 void loadReferenceNames(List<ConditionalVariable> varList, TreeSet<String> treeSet) { 1360 treeSet.clear(); 1361 for (ConditionalVariable condVar : varList) { 1362 if (condVar.getType() == Conditional.Type.CONDITIONAL_TRUE 1363 || condVar.getType() == Conditional.Type.CONDITIONAL_FALSE) { 1364 treeSet.add(condVar.getName()); 1365 } 1366 } 1367 } 1368 1369 boolean checkConditionalUserName(String uName, Logix logix) { 1370 if ((uName != null) && (!(uName.isEmpty()))) { 1371 Conditional p = _conditionalManager.getByUserName(logix, uName); 1372 if (p != null) { 1373 // Conditional with this user name already exists 1374 log.error("Failure to update Conditional with Duplicate User Name: {}", uName); 1375 JmriJOptionPane.showMessageDialog(getFrame(), 1376 Bundle.getMessage("LogixError10"), // NOI18N 1377 Bundle.getMessage("ErrorTitle"), // NOI18N 1378 JmriJOptionPane.ERROR_MESSAGE); 1379 return false; 1380 } 1381 } 1382 return true; 1383 } 1384 1385 /** 1386 * Check form of Conditional systemName. 1387 * 1388 * @param sName system name of bean to be checked 1389 * @return false if sName is empty string or null 1390 */ 1391 boolean checkConditionalSystemName(@CheckForNull String sName) { 1392 if ( sName != null && !sName.isEmpty() ) { 1393 Conditional p = _conditionalManager.getBySystemName(sName); 1394 if (p != null) { 1395 return false; 1396 } 1397 } else { 1398 return false; 1399 } 1400 return true; 1401 } 1402 1403 /** 1404 * Check for conditional references. 1405 * 1406 * @since 4.7.4 1407 * @param logixName The Logix under consideration 1408 * @return true if no references 1409 */ 1410 boolean checkConditionalReferences(String logixName) { 1411 _saveTargetList.clear(); 1412 Logix x = _logixManager.getLogix(logixName); 1413 int numConditionals = x.getNumConditionals(); 1414 if (numConditionals > 0) { 1415 for (int i = 0; i < numConditionals; i++) { 1416 String csName = x.getConditionalByNumberOrder(i); 1417 1418 // If the conditional is a where used source, retain it for later 1419 ArrayList<String> targetList = InstanceManager.getDefault(ConditionalManager.class) 1420 .getTargetList(csName); 1421 if (!targetList.isEmpty()) { 1422 _saveTargetList.put(csName, targetList); 1423 } 1424 1425 // If the conditional is a where used target, check scope 1426 ArrayList<String> refList = InstanceManager.getDefault(ConditionalManager.class).getWhereUsed(csName); 1427 if (refList != null) { 1428 for (String refName : refList) { 1429 Logix xRef = _conditionalManager.getParentLogix(refName); 1430 String xsName = xRef.getSystemName(); 1431 if (logixName.equals(xsName)) { 1432 // Member of the same Logix 1433 continue; 1434 } 1435 1436 // External references have to be removed before the Logix can be deleted. 1437 Conditional c = x.getConditional(csName); 1438 Conditional cRef = xRef.getConditional(refName); 1439 JmriJOptionPane.showMessageDialog(getFrame(), 1440 Bundle.getMessage("LogixError11", c.getUserName(), c.getSystemName(),cRef.getUserName(), 1441 cRef.getSystemName(), xRef.getUserName(), xRef.getSystemName()), // NOI18N 1442 Bundle.getMessage("ErrorTitle"), 1443 JmriJOptionPane.ERROR_MESSAGE); // NOI18N 1444 return false; 1445 } 1446 } 1447 } 1448 } 1449 return true; 1450 } 1451 1452 /** 1453 * Remove target/source where used entries after a Logix delete. 1454 * 1455 * @since 4.7.4 1456 */ 1457 void deleteSourceWhereUsed() { 1458 _saveTargetList.forEach((refName, targetList) -> { 1459 for (String targetName : targetList) { 1460 InstanceManager.getDefault(ConditionalManager.class).removeWhereUsed(targetName, refName); 1461 } 1462 }); 1463 } 1464 1465 /** 1466 * Update the conditional reference where used. 1467 * <p> 1468 * The difference between the saved target names and new target names is 1469 * used to add/remove where used references. 1470 * 1471 * @since 4.7.4 1472 * @param newTargetNames The conditional target names after updating 1473 * @param refName The system name for the referencing conditional 1474 */ 1475 void updateWhereUsed(TreeSet<String> newTargetNames, String refName) { 1476 TreeSet<String> deleteNames = new TreeSet<>(_saveTargetNames); 1477 deleteNames.removeAll(newTargetNames); 1478 for (String deleteName : deleteNames) { 1479 InstanceManager.getDefault(ConditionalManager.class).removeWhereUsed(deleteName, refName); 1480 } 1481 1482 TreeSet<String> addNames = new TreeSet<>(newTargetNames); 1483 addNames.removeAll(_saveTargetNames); 1484 for (String addName : addNames) { 1485 InstanceManager.getDefault(ConditionalManager.class).addWhereUsed(addName, refName); 1486 } 1487 } 1488 1489 /** 1490 * Create Variable and Action editing pane center part. 1491 * 1492 * @param comp Field or comboBox to include on sub pane 1493 * @param label property key for label 1494 * @param hint property key for tooltip for this sub pane 1495 * @return JPanel containing interface 1496 */ 1497 JPanel makeEditPanel(JComponent comp, String label, String hint) { 1498 JPanel panel = new JPanel(); 1499 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 1500 JPanel p = new JPanel(); 1501 p.add(new JLabel(Bundle.getMessage(label))); 1502 panel.add(p); 1503 if (hint != null) { 1504 panel.setToolTipText(Bundle.getMessage(hint)); 1505 } 1506 comp.setMaximumSize(comp.getPreferredSize()); // override for text fields 1507 panel.add(comp); 1508 panel.add(Box.createVerticalGlue()); 1509 return panel; 1510 } 1511 1512 /** 1513 * Format time to hh:mm given integer hour and minute. 1514 * 1515 * @param hour value for time hours 1516 * @param minute value for time minutes 1517 * @return Formatted time string 1518 */ 1519 public static String formatTime(int hour, int minute) { 1520 String s = ""; 1521 String t = Integer.toString(hour); 1522 if (t.length() == 2) { 1523 s = t + ":"; 1524 } else if (t.length() == 1) { 1525 s = "0" + t + ":"; 1526 } 1527 t = Integer.toString(minute); 1528 if (t.length() == 2) { 1529 s += t; 1530 } else if (t.length() == 1) { 1531 s = s + "0" + t; 1532 } 1533 if (s.length() != 5) { 1534 // input error 1535 s = "00:00"; 1536 } 1537 return s; 1538 } 1539 1540 @Override 1541 public String getClassDescription() { 1542 return Bundle.getMessage("TitleLogixTable"); // NOI18N 1543 } 1544 1545 @Override 1546 protected String getClassName() { 1547 return LogixTableAction.class.getName(); 1548 } 1549 1550 // ------------ Methods for Conditional References Window ------------ 1551 /** 1552 * Builds the conditional references window when the Conditional Variable 1553 * References menu item is selected. 1554 * <p> 1555 * This is a stand-alone window that can be closed at any time. 1556 * 1557 * @since 4.7.4 1558 */ 1559 void makeWhereUsedWindow() { 1560 1561 JmriJFrame referenceListFrame = new JmriJFrame(Bundle.getMessage("LabelRefTitle"), false, true); // NOI18N 1562 Container contentPane = referenceListFrame.getContentPane(); 1563 contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); 1564 1565 // build header information 1566 JPanel panel1 = new JPanel(); 1567 panel1.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 5)); 1568 panel1.add(new JLabel(Bundle.getMessage("LabelRefTarget"))); // NOI18N 1569 panel1.add(new JLabel(Bundle.getMessage("LabelRefSource"))); // NOI18N 1570 contentPane.add(panel1); 1571 1572 // Build the conditional references listing 1573 JTextArea textContent = buildWhereUsedListing(); 1574 JScrollPane scrollPane = new JScrollPane(textContent); 1575 contentPane.add(scrollPane); 1576 1577 referenceListFrame.pack(); 1578 referenceListFrame.setVisible(true); 1579 } 1580 1581 /** 1582 * Creates a component containing the conditional reference where used list. 1583 * The source is {@link jmri.ConditionalManager#getWhereUsedMap()} 1584 * 1585 * @return a TextArea, empty if reference is not used 1586 * @since 4.7.4 1587 */ 1588 public JTextArea buildWhereUsedListing() { 1589 JTextArea condText = new javax.swing.JTextArea(); 1590 condText.setText(null); 1591 HashMap<String, ArrayList<String>> whereUsed = 1592 InstanceManager.getDefault(ConditionalManager.class).getWhereUsedMap(); 1593 SortedSet<String> targets = new TreeSet<>(whereUsed.keySet()); 1594 targets.forEach( target -> { 1595 condText.append("\n" + target + "\t" + getWhereUsedName(target) + " \n"); 1596 ArrayList<String> refNames = whereUsed.get(target); 1597 refNames.forEach( refName -> 1598 condText.append("\t\t" + refName + "\t" + getWhereUsedName(refName) + " \n")); 1599 }); 1600 condText.setCaretPosition(0); 1601 condText.setTabSize(2); 1602 condText.setEditable(false); 1603 return condText; 1604 } 1605 1606 String getWhereUsedName(String cName) { 1607 Conditional cond = _conditionalManager.getBySystemName(cName); 1608 if ( cond!=null){ 1609 return cond.getUserName(); 1610 } 1611 return ""; 1612 } 1613 1614// ------------ Methods for Conditional Browser Window ------------ 1615 /** 1616 * Respond to the Browse button pressed in Logix table. 1617 * 1618 * @param sName The selected Logix system name 1619 */ 1620 void browserPressed(String sName) { 1621 makeBrowserWindow(sName); 1622 } 1623 1624 /** 1625 * Create and initialize the conditionals browser window. 1626 * @param lgxName Logix system name 1627 */ 1628 void makeBrowserWindow(String lgxName) { 1629 Logix logix = _logixManager.getBySystemName(lgxName); 1630 if (logix == null) { 1631 return; 1632 } 1633 // Logix was found, create the window 1634 JmriJFrame condBrowserFrame = new JmriJFrame(Bundle.getMessage("BrowserTitle"), false, true); // NOI18N 1635 condBrowserFrame.addHelpMenu("package.jmri.jmrit.beantable.LogixAddEdit", true); // NOI18N 1636 1637 Container contentPane = condBrowserFrame.getContentPane(); 1638 contentPane.setLayout(new BorderLayout()); 1639 1640 // LOGIX header information 1641 JPanel topPanel = new JPanel(); 1642 String tStr = Bundle.getMessage("BrowserLogix") + " " + logix.getSystemName() + " " // NOI18N 1643 + logix.getUserName() + " " 1644 + (logix.getEnabled() 1645 ? Bundle.getMessage("BrowserEnabled") // NOI18N 1646 : Bundle.getMessage("BrowserDisabled")); // NOI18N 1647 topPanel.add(new JLabel(tStr)); 1648 contentPane.add(topPanel, BorderLayout.NORTH); 1649 1650 // Build the conditionals listing 1651 JTextArea textContent = buildConditionalListing(logix); 1652 JScrollPane scrollPane = new JScrollPane(textContent); 1653 contentPane.add(scrollPane); 1654 1655 JPanel bottomPanel = new JPanel(); 1656 bottomPanel.setLayout(new BorderLayout()); 1657 JButton helpBrowse = new JButton(Bundle.getMessage("MenuHelp")); // NOI18N 1658 bottomPanel.add(helpBrowse, BorderLayout.WEST); 1659 helpBrowse.addActionListener( e -> 1660 JmriJOptionPane.showMessageDialog(condBrowserFrame, 1661 Bundle.getMessage("BrowserHelpText"), // NOI18N 1662 Bundle.getMessage("BrowserHelpTitle"), // NOI18N 1663 JmriJOptionPane.INFORMATION_MESSAGE)); 1664 JButton saveBrowse = new JButton(Bundle.getMessage("BrowserSaveButton")); // NOI18N 1665 saveBrowse.setToolTipText(Bundle.getMessage("BrowserSaveButtonHint")); // NOI18N 1666 bottomPanel.add(saveBrowse, BorderLayout.EAST); 1667 saveBrowse.addActionListener(new SaveAction(lgxName)); 1668 contentPane.add(bottomPanel, BorderLayout.SOUTH); 1669 1670 condBrowserFrame.pack(); 1671 condBrowserFrame.setVisible(true); 1672 } // makeBrowserWindow 1673 1674 private class SaveAction implements ActionListener { 1675 String _lgxName; 1676 SaveAction(String lgxName) { 1677 _lgxName = lgxName; 1678 } 1679 @Override 1680 public void actionPerformed(ActionEvent e) { 1681 saveBrowserPressed(_lgxName); 1682 } 1683 } 1684 1685 /** 1686 * Save the Logix browser window content to a text file. 1687 * @param lgxName Logix system name 1688 */ 1689 void saveBrowserPressed(String lgxName) { 1690 Logix logix = _logixManager.getBySystemName(lgxName); 1691 if (logix == null) { 1692 log.warn("Can't save browsed data, logix {} no longer exits", lgxName); 1693 return; 1694 } 1695 JFileChooser userFileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath()); 1696 userFileChooser.setApproveButtonText(Bundle.getMessage("BrowserSaveDialogApprove")); // NOI18N 1697 userFileChooser.setDialogTitle(Bundle.getMessage("BrowserSaveDialogTitle")); // NOI18N 1698 userFileChooser.rescanCurrentDirectory(); 1699 // Default to logix system name.txt 1700 userFileChooser.setSelectedFile(new File(FileUtil.sanitizeFilename(logix.getSystemName()) + ".txt")); // NOI18N 1701 int retVal = userFileChooser.showSaveDialog(null); 1702 if (retVal != JFileChooser.APPROVE_OPTION) { 1703 log.debug("Save browser content stopped, no file selected"); // NOI18N 1704 return; // give up if no file selected or cancel pressed 1705 } 1706 File file = userFileChooser.getSelectedFile(); 1707 log.debug("Save browser content to '{}'", file); // NOI18N 1708 1709 if (file.exists()) { 1710 Object[] options = {Bundle.getMessage("BrowserSaveDuplicateReplace"), // NOI18N 1711 Bundle.getMessage("BrowserSaveDuplicateAppend"), // NOI18N 1712 Bundle.getMessage("ButtonCancel")}; // NOI18N 1713 int selectedOption = JmriJOptionPane.showOptionDialog(null, 1714 Bundle.getMessage("BrowserSaveDuplicatePrompt", file.getName()), // NOI18N 1715 Bundle.getMessage("BrowserSaveDuplicateTitle"), // NOI18N 1716 JmriJOptionPane.DEFAULT_OPTION, 1717 JmriJOptionPane.WARNING_MESSAGE, 1718 null, options, options[0]); 1719 if (selectedOption == 2 || selectedOption == -1) { 1720 log.debug("Save browser content stopped, file replace/append cancelled"); // NOI18N 1721 return; // Cancel selected or dialog box closed 1722 } 1723 if (selectedOption == 0) { 1724 FileUtil.delete(file); // Replace selected 1725 } 1726 } 1727 1728 // Create the file content 1729 String tStr = Bundle.getMessage("BrowserLogix") + " " + logix.getSystemName() + " " // NOI18N 1730 + logix.getUserName() + " " 1731 + (logix.getEnabled() 1732 ? Bundle.getMessage("BrowserEnabled") // NOI18N 1733 : Bundle.getMessage("BrowserDisabled")); // NOI18N 1734 JTextArea textContent = buildConditionalListing(logix); 1735 try { 1736 // ADD Logix Header inforation first 1737 FileUtil.appendTextToFile(file, tStr); 1738 FileUtil.appendTextToFile(file, textContent.getText()); 1739 } catch (IOException e) { 1740 log.error("Unable to write browser content to '{}'", file, e); // NOI18N 1741 } 1742 } 1743 1744 /** 1745 * Builds a Component representing the current conditionals for the selected 1746 * Logix statement. 1747 * 1748 *@param logix browsing Logix 1749 * @return a TextArea listing existing conditionals; will be empty if there 1750 * are none 1751 */ 1752 JTextArea buildConditionalListing(Logix logix) { 1753 String showSystemName; 1754 String showCondName; 1755 String condName; 1756 String operand; 1757 String tStr; 1758 1759 List<ConditionalVariable> variableList; 1760 List<ConditionalAction> actionList; 1761 ConditionalVariable variable; 1762 ConditionalAction action; 1763 String _antecedent; 1764 1765 JTextArea condText = new javax.swing.JTextArea(); 1766 condText.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 1767 condText.setText(null); 1768 int numConditionals = logix.getNumConditionals(); 1769 for (int rx = 0; rx < numConditionals; rx++) { 1770 1771 Conditional curConditional = _conditionalManager.getBySystemName(logix.getConditionalByNumberOrder(rx)); 1772 if (curConditional==null){ 1773 continue; 1774 } 1775 variableList = curConditional.getCopyOfStateVariables(); 1776 actionList = curConditional.getCopyOfActions(); 1777 1778 showCondName = curConditional.getUserName(); 1779 if (showCondName == null) { 1780 showCondName = ""; 1781 } 1782 showSystemName = curConditional.getSystemName(); 1783 1784 // If no user name for a conditional, create one using C + row number 1785 if (showCondName.isEmpty()) { 1786 showCondName = "C" + (rx + 1); 1787 } 1788 condText.append("\n " + showSystemName + " " + showCondName + " \n"); 1789 if (curConditional.getLogicType() == Conditional.AntecedentOperator.MIXED) { 1790 _antecedent = curConditional.getAntecedentExpression(); 1791 String antecedent = ConditionalEditBase.translateAntecedent(_antecedent, false); 1792 condText.append(" " + Bundle.getMessage("LogixAntecedent") + " " + antecedent + " \n"); // NOI18N 1793 } 1794 1795 for (int i = 0; i < variableList.size(); i++) { 1796 variable = variableList.get(i); 1797 String varTrigger = (variable.doTriggerActions()) 1798 ? "[x]" // NOI18N 1799 : "[ ]"; 1800 tStr = " " + varTrigger + " "; 1801 tStr = tStr + " R" + (i + 1) + (i > 8 ? " " : " "); // Makes {Rx}bb or {Rxx}b 1802 condText.append(tStr); 1803 1804 operand = variable.getOpernString(); 1805 if (i == 0) { // add the IF to the first conditional 1806 condText.append(Bundle.getMessage("BrowserIF") + " " + operand + " "); // NOI18N 1807 } else { 1808 condText.append(" " + operand + " "); 1809 } 1810 if (variable.isNegated()) { 1811 condText.append(Bundle.getMessage("LogicNOT") + " "); // NOI18N 1812 } 1813 condText.append(variable.toString() + " \n"); 1814 } // for _variableList 1815 1816 if (!actionList.isEmpty()) { 1817 condText.append(" " + Bundle.getMessage("BrowserTHEN") + " \n"); // NOI18N 1818 boolean triggerType = curConditional.getTriggerOnChange(); 1819 for (int i = 0; i < actionList.size(); i++) { 1820 action = actionList.get(i); 1821 condName = action.description(triggerType); 1822 condText.append(" " + condName + " \n"); 1823 } 1824 } else { 1825 condText.append(" " + Bundle.getMessage("BrowserNoAction") + " \n\n"); // NOI18N 1826 } 1827 } // for numConditionals 1828 1829 condText.setCaretPosition(0); 1830 condText.setTabSize(4); 1831 condText.setEditable(false); 1832 return condText; 1833 } // buildConditionalListing 1834 1835 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixTableAction.class); 1836 1837}