001package jmri.jmrit.beantable; 002 003import java.awt.*; 004import java.awt.event.*; 005import java.io.File; 006import java.io.IOException; 007import java.util.*; 008import java.util.List; 009 010import javax.annotation.Nonnull; 011import javax.swing.*; 012import javax.swing.table.TableColumn; 013 014import jmri.InstanceManager; 015import jmri.Manager; 016import jmri.NamedBean; 017import jmri.NamedBean.BadSystemNameException; 018import jmri.NamedBean.BadUserNameException; 019import jmri.UserPreferencesManager; 020import jmri.jmrit.logixng.Base; 021import jmri.jmrit.logixng.tools.swing.AbstractLogixNGEditor; 022import jmri.jmrit.logixng.tools.swing.DeleteBean; 023import jmri.util.FileUtil; 024import jmri.util.JmriJFrame; 025import jmri.util.swing.JmriJOptionPane; 026 027/** 028 * Swing action to create and register a LogixNG Table. 029 * <p> 030 Also contains the panes to create, edit, and delete a LogixNG. 031 <p> 032 * Most of the text used in this GUI is in BeanTableBundle.properties, accessed 033 * via Bundle.getMessage(). 034 * 035 * @author Dave Duchamp Copyright (C) 2007 (LogixTableAction) 036 * @author Pete Cressman Copyright (C) 2009, 2010, 2011 (LogixTableAction) 037 * @author Matthew Harris copyright (c) 2009 (LogixTableAction) 038 * @author Dave Sand copyright (c) 2017 (LogixTableAction) 039 * @author Daniel Bergqvist copyright (c) 2019 (AbstractLogixNGTableEditor) 040 * @author Dave Sand copyright (c) 2021 (AbstractLogixNGTableEditor) 041 * 042 * @param <E> the type of NamedBean supported by this model 043 */ 044public abstract class AbstractLogixNGTableAction<E extends NamedBean> extends AbstractTableAction<E> { 045 046 047 private static final ResourceBundle rbx = ResourceBundle.getBundle("jmri.jmrit.logixng.LogixNGBundle"); 048 private static final ResourceBundle rbx2 = ResourceBundle.getBundle("jmri.jmrit.logixng.tools.swing.LogixNGSwingBundle"); 049 050 // Browser Options 051 static final String PRINT_LINE_NUMBERS_OPTION = "jmri.jmrit.logixng.PrintLineNumbers"; 052 static final String PRINT_ERROR_HANDLING_OPTION = "jmri.jmrit.logixng.ErrorHandling"; 053 static final String PRINT_NOT_CONNECTED_OPTION = "jmri.jmrit.logixng.NotConnectedSockets"; 054 static final String PRINT_LOCAL_VARIABLES_OPTION = "jmri.jmrit.logixng.LocalVariables"; 055 static final String PRINT_SYSTEM_NAMES_OPTION = "jmri.jmrit.logixng.SystemNames"; 056 057 JTextArea _textContent; 058 DeleteBean<E> deleteBean = new DeleteBean<>(getManager()); 059 060 /** 061 * Create a AbstractLogixNGTableAction instance. 062 * 063 * @param s the Action title, not the title of the resulting frame. Perhaps 064 * this should be changed? 065 */ 066 public AbstractLogixNGTableAction(String s) { 067 super(s); 068 super.setEnabled(false); 069 } 070 071 protected abstract AbstractLogixNGEditor<E> getEditor(BeanTableDataModel<E> m, String sName); 072 073 protected boolean isEditSupported() { 074 return true; 075 } 076 077 @Nonnull 078 @Override 079 protected abstract Manager<E> getManager(); 080 081 protected abstract void enableAll(boolean enable); 082 083 protected abstract void setEnabled(E bean, boolean enable); 084 085 protected abstract boolean isEnabled(E bean); 086 087 protected abstract E createBean(String userName); 088 089 protected abstract E createBean(String systemName, String userName); 090 091 protected abstract void deleteBean(E bean); 092 093 protected boolean browseMonoSpace() { return false; } 094 095 protected abstract String getBeanText(E bean); 096 097 protected abstract String getBrowserTitle(); 098 099 protected abstract String getAddTitleKey(); 100 101 protected abstract String getCreateButtonHintKey(); 102 103 protected abstract void getListenerRefsIncludingChildren(E t, List<String> list); 104 105 protected abstract boolean hasChildren(E t); 106 107 // ------------ Methods for LogixNG Table Window ------------ 108 109 /** 110 * Create the JTable DataModel, along with the changes (overrides of 111 * BeanTableDataModel) for the specific case of a LogixNG table. 112 */ 113 @Override 114 protected void createModel() { 115 m = new TableModel(); 116 } 117 118 /** 119 * Set title of NamedBean table. 120 */ 121 @Override 122 protected void setTitle() { 123 f.setTitle(Bundle.getMessage("TitleLogixNGTable")); 124 } 125 126 /** 127 * Insert 2 table specific menus. 128 * <p> 129 * Accounts for the Window and Help menus, which are already added to the 130 * menu bar as part of the creation of the JFrame, by adding the new menus 2 131 * places earlier unless the table is part of the ListedTableFrame, which 132 * adds the Help menu later on. 133 * 134 * @param f the JFrame of this table 135 */ 136 @Override 137 public void setMenuBar(BeanTableFrame<E> f) { 138 JMenu menu = new JMenu(Bundle.getMessage("MenuOptions")); // NOI18N 139 menu.setMnemonic(KeyEvent.VK_O); 140 javax.swing.JMenuBar menuBar = f.getJMenuBar(); 141 int pos = menuBar.getMenuCount() - 1; // count the number of menus to insert the TableMenus before 'Window' and 'Help' 142 int offset = 1; 143 log.debug("setMenuBar number of menu items = {}", pos); // NOI18N 144 for (int i = 0; i <= pos; i++) { 145 if (menuBar.getComponent(i) instanceof JMenu) { 146 if (((JMenu) menuBar.getComponent(i)).getText().equals(Bundle.getMessage("MenuHelp"))) { // NOI18N 147 offset = -1; // correct for use as part of ListedTableAction where the Help Menu is not yet present 148 } 149 } 150 } 151 152 // Do not include this menu for Module or Table tables 153 if (this instanceof LogixNGTableAction) { 154 JMenuItem r = new JMenuItem(Bundle.getMessage("EnableAllLogixNGs")); // NOI18N 155 r.addActionListener((ActionEvent e) -> { 156 enableAll(true); 157 }); 158 menu.add(r); 159 160 r = new JMenuItem(Bundle.getMessage("DisableAllLogixNGs")); // NOI18N 161 r.addActionListener((ActionEvent e) -> { 162 enableAll(false); 163 }); 164 menu.add(r); 165 166 menuBar.add(menu, pos + offset); 167 offset++; 168 } 169 170 menu = new JMenu(Bundle.getMessage("MenuTools")); // NOI18N 171 menu.setMnemonic(KeyEvent.VK_T); 172 173 JMenuItem item = new JMenuItem(rbx2.getString("MenuOpenClipboard")); // NOI18N 174 item.addActionListener((ActionEvent e) -> { 175 jmri.jmrit.logixng.tools.swing.TreeEditor.openClipboard(); 176 }); 177 menu.add(item); 178 179 item = new JMenuItem(Bundle.getMessage("OpenPickListTables")); // NOI18N 180 item.addActionListener((ActionEvent e) -> { 181 openPickListTable(); 182 }); 183 menu.add(item); 184 185 menuBar.add(menu, pos + offset); // add this menu to the right of the previous 186 } 187 188 /** 189 * Open a new Pick List to drag Actions from to form NamedBean. 190 */ 191 private void openPickListTable() { 192 if (_pickTables == null) { 193 _pickTables = new jmri.jmrit.picker.PickFrame(Bundle.getMessage("TitlePickList")); // NOI18N 194 } else { 195 _pickTables.setVisible(true); 196 } 197 _pickTables.toFront(); 198 } 199 200 @Override 201 protected String helpTarget() { 202 return "package.jmri.jmrit.beantable.LogixNGTable"; // NOI18N 203 } 204 205 // ------------ variable definitions ------------ 206 207 protected AbstractLogixNGEditor<E> _editor = null; 208 209 boolean _showReminder = false; 210 private boolean _checkEnabled = jmri.InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).isStoreCheckEnabled(); 211 jmri.jmrit.picker.PickFrame _pickTables; 212 213 // Current focus variables 214 protected E _curNamedBean = null; 215 int conditionalRowNumber = 0; 216 217 protected final Base.PrintTreeSettings _printTreeSettings = new Base.PrintTreeSettings(); 218 219 // Add E Variables 220 JmriJFrame addLogixNGFrame = null; 221 JTextField _systemName = new JTextField(20); 222 JTextField _addUserName = new JTextField(20); 223 JCheckBox _autoSystemName = new JCheckBox(Bundle.getMessage("LabelAutoSysName")); // NOI18N 224 JLabel _sysNameLabel = new JLabel(rbx.getString("BeanNameLogixNG") + " " + Bundle.getMessage("ColumnSystemName") + ":"); // NOI18N 225 JLabel _userNameLabel = new JLabel(rbx.getString("BeanNameLogixNG") + " " + Bundle.getMessage("ColumnUserName") + ":"); // NOI18N 226 String systemNameAuto = this.getClassName() + ".AutoSystemName"; // NOI18N 227 JButton create; 228 229 // Edit E Variables 230 private boolean _inEditMode = false; 231 private boolean _inCopyMode = false; 232 233 // ------------ Methods for Add bean Window ------------ 234 235 /** 236 * Respond to the Add button in bean table Creates and/or initialize 237 * the Add bean pane. 238 * 239 * @param e The event heard 240 */ 241 @Override 242 protected void addPressed(ActionEvent e) { 243 // possible change 244 if (!checkFlags(null)) { 245 return; 246 } 247 _showReminder = true; 248 // make an Add bean Frame 249 if (addLogixNGFrame == null) { 250 String titleKey = getAddTitleKey(); 251 String buttonHintKey = getCreateButtonHintKey(); 252 JPanel panel5 = makeAddFrame(titleKey, "Add"); // NOI18N 253 // Create bean 254 create = new JButton(Bundle.getMessage("ButtonCreate")); // NOI18N 255 panel5.add(create); 256 create.addActionListener(this::createPressed); 257 create.setToolTipText(Bundle.getMessage(buttonHintKey)); // NOI18N 258 } 259 addLogixNGFrame.pack(); 260 addLogixNGFrame.setVisible(true); 261 _autoSystemName.setSelected(false); 262 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> { 263 _autoSystemName.setSelected(prefMgr.getCheckboxPreferenceState(systemNameAuto, true)); 264 }); 265 } 266 267 protected abstract JPanel makeAddFrame(String titleId, String startMessageId); 268 269 /** 270 * Enable/disable fields for data entry when user selects to have system 271 * name automatically generated. 272 */ 273 void autoSystemName() { 274 if (_autoSystemName.isSelected()) { 275 _systemName.setEnabled(false); 276 _sysNameLabel.setEnabled(false); 277 } else { 278 _systemName.setEnabled(true); 279 _sysNameLabel.setEnabled(true); 280 } 281 } 282 283 /** 284 * Respond to the Cancel button in Add bean window. 285 * <p> 286 * Note: Also get there if the user closes the Add bean window. 287 * 288 * @param e The event heard 289 */ 290 void cancelAddPressed(ActionEvent e) { 291 addLogixNGFrame.setVisible(false); 292 addLogixNGFrame.dispose(); 293 addLogixNGFrame = null; 294 _inCopyMode = false; 295 if (f != null) { 296 f.setVisible(true); 297 } 298 } 299 300 /** 301 * Respond to the Copy bean button in Add bean window. 302 * <p> 303 * Provides a pane to set new properties of the copy. 304 * 305 * @param sName system name of bean to be copied 306 */ 307 void copyPressed(String sName) { 308 if (!checkFlags(sName)) { 309 return; 310 } 311 312 Runnable t = new Runnable() { 313 @Override 314 public void run() { 315// JmriJOptionPane.showMessageDialog(null, "Copy is not implemented yet.", "Error", JmriJOptionPane.ERROR_MESSAGE); 316 317 JPanel panel5 = makeAddFrame("TitleCopyLogixNG", "Copy"); // NOI18N 318 // Create bean 319 JButton create = new JButton(Bundle.getMessage("ButtonCopy")); // NOI18N 320 panel5.add(create); 321 create.addActionListener((ActionEvent e) -> { 322 copyBeanPressed(e); 323 }); 324 addLogixNGFrame.pack(); 325 addLogixNGFrame.setVisible(true); 326 _autoSystemName.setSelected(false); 327 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> { 328 _autoSystemName.setSelected(prefMgr.getCheckboxPreferenceState(systemNameAuto, true)); 329 }); 330 331 _inCopyMode = false; 332 } 333 }; 334 log.debug("copyPressed started for {}", sName); // NOI18N 335 javax.swing.SwingUtilities.invokeLater(t); 336 _inCopyMode = true; 337 _logixNGSysName = sName; 338 } 339 340 String _logixNGSysName; 341 342 protected void copyBean(@Nonnull E sourceBean, @Nonnull E targetBean) { 343 throw new UnsupportedOperationException("Not implemented"); 344 } 345 346 protected boolean isCopyBeanSupported() { 347 return false; 348 } 349 350 protected boolean isExecuteSupported() { 351 return false; 352 } 353 354 protected void execute(@Nonnull E bean) { 355 throw new UnsupportedOperationException("Not implemented"); 356 } 357 358 /** 359 * Copy the bean as configured in the Copy set up pane. 360 * 361 * @param e the event heard 362 */ 363 private void copyBeanPressed(ActionEvent e) { 364 365 String uName = _addUserName.getText().trim(); 366 if (uName.length() == 0) { 367 uName = null; 368 } 369 E targetBean; 370 if (_autoSystemName.isSelected()) { 371 if (!checkLogixNGUserName(uName)) { 372 return; 373 } 374 targetBean = createBean(uName); 375 } else { 376 if (!checkLogixNGSysName()) { 377 return; 378 } 379 String sName = _systemName.getText().trim(); 380 // check if a bean with this name already exists 381 boolean createLogix = true; 382 targetBean = getManager().getBySystemName(sName); 383 if (targetBean != null) { 384 int result = JmriJOptionPane.showConfirmDialog(f, 385 Bundle.getMessage("ConfirmLogixDuplicate", sName, _logixNGSysName), // NOI18N 386 Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION, // NOI18N 387 JmriJOptionPane.QUESTION_MESSAGE); 388 if (JmriJOptionPane.NO_OPTION == result) { 389 return; 390 } 391 createLogix = false; 392 String userName = targetBean.getUserName(); 393 if (userName != null && userName.length() > 0) { 394 _addUserName.setText(userName); 395 uName = userName; 396 } 397 } else if (!checkLogixNGUserName(uName)) { 398 return; 399 } 400 if (createLogix) { 401 // Create the new LogixNG 402 targetBean = createBean(sName, uName); 403 if (targetBean == null) { 404 // should never get here unless there is an assignment conflict 405 log.error("Failure to create LogixNG with System Name: {}", sName); // NOI18N 406 return; 407 } 408 } else if (targetBean == null) { 409 log.error("Error targetLogix is null!"); // NOI18N 410 return; 411 } else { 412 targetBean.setUserName(uName); 413 } 414 } 415 E sourceBean = getManager().getBySystemName(_logixNGSysName); 416 if (sourceBean != null) copyBean(sourceBean, targetBean); 417 else log.error("Error targetLogix is null!"); // NOI18N 418 cancelAddPressed(null); 419 } 420 421 /** 422 * Check and warn if a string is already in use as the user name of a LogixNG. 423 * 424 * @param uName the suggested name 425 * @return true if not in use 426 */ 427 boolean checkLogixNGUserName(String uName) { 428 // check if a bean with the same user name exists 429 if (uName != null && uName.trim().length() > 0) { 430 E x = getManager().getByUserName(uName); 431 if (x != null) { 432 // A bean with this user name already exists 433 JmriJOptionPane.showMessageDialog(addLogixNGFrame, 434 Bundle.getMessage("LogixNGError3"), Bundle.getMessage("ErrorTitle"), // NOI18N 435 JmriJOptionPane.ERROR_MESSAGE); 436 return false; 437 } 438 } 439 return true; 440 } 441 442 /** 443 * Check validity of bean system name. 444 * <p> 445 * Fixes name if it doesn't start with "IQ". 446 * 447 * @return false if name has length < 1 after displaying a dialog 448 */ 449 boolean checkLogixNGSysName() { 450 String sName = _systemName.getText(); 451 if ((sName.length() < 1)) { 452 // Entered system name is blank or too short 453 JmriJOptionPane.showMessageDialog(addLogixNGFrame, 454 Bundle.getMessage("LogixNGError8"), Bundle.getMessage("ErrorTitle"), // NOI18N 455 JmriJOptionPane.ERROR_MESSAGE); 456 return false; 457 } 458 if ((sName.length() < 2) || (sName.charAt(0) != 'I') 459 || (sName.charAt(1) != 'Q')) { 460 // System name does not begin with IQ:, prefix IQ: to it 461 String s = sName; 462 sName = "IQ" + s; // NOI18N 463 } 464 _systemName.setText(sName); 465 return true; 466 } 467 468 /** 469 * Check if another bean editing session is currently open or no system 470 * name is provided. 471 * 472 * @param sName system name of bean to be copied 473 * @return true if a new session may be started 474 */ 475 boolean checkFlags(String sName) { 476 if (_inEditMode) { 477 // Already editing a bean, ask for completion of that edit 478 JmriJOptionPane.showMessageDialog(null, 479 Bundle.getMessage("LogixNGError32", _curNamedBean.getSystemName()), 480 Bundle.getMessage("ErrorTitle"), 481 JmriJOptionPane.ERROR_MESSAGE); 482 if (_editor != null) { 483 _editor.bringToFront(); 484 } 485 return false; 486 } 487 488 if (_inCopyMode) { 489 // Already editing a bean, ask for completion of that edit 490 JmriJOptionPane.showMessageDialog(null, 491 Bundle.getMessage("LogixNGError31", _logixNGSysName), 492 Bundle.getMessage("ErrorTitle"), // NOI18N 493 JmriJOptionPane.ERROR_MESSAGE); 494 return false; 495 } 496 497 if (sName != null) { 498 // check if a bean with this name exists 499 E x = getManager().getBySystemName(sName); 500 if (x == null) { 501 // bean does not exist, so cannot be edited 502 log.error("No bean with system name: {}", sName); 503 JmriJOptionPane.showMessageDialog(null, 504 Bundle.getMessage("LogixNGError5"), 505 Bundle.getMessage("ErrorTitle"), // NOI18N 506 JmriJOptionPane.ERROR_MESSAGE); 507 return false; 508 } 509 } 510 return true; 511 } 512 513 /** 514 * Respond to the Create bean button in Add bean window. 515 * 516 * @param e The event heard 517 */ 518 void createPressed(ActionEvent e) { 519 // possible change 520 _showReminder = true; 521 String sName; 522 String uName = _addUserName.getText().trim(); 523 if (uName.length() == 0) { 524 uName = null; 525 } 526 if (_autoSystemName.isSelected()) { 527 if (!checkLogixNGUserName(uName)) { 528 return; 529 } 530 try { 531 _curNamedBean = createBean(uName); 532 } catch (BadSystemNameException | BadUserNameException ex) { 533 JmriJOptionPane.showMessageDialog(addLogixNGFrame, ex.getLocalizedMessage(), 534 Bundle.getMessage("ErrorTitle"), // NOI18N 535 JmriJOptionPane.ERROR_MESSAGE); 536 return; 537 } 538 if (_curNamedBean == null) { 539 log.error("Failure to create bean with System Name: {}", "none"); // NOI18N 540 return; 541 } 542 sName = _curNamedBean.getSystemName(); 543 } else { 544 if (!checkLogixNGSysName()) { 545 return; 546 } 547 // Get validated system name 548 sName = _systemName.getText(); 549 // check if a bean with this name already exists 550 E x; 551 try { 552 x = getManager().getBySystemName(sName); 553 } catch (Exception ex) { 554 // user input no good 555 handleCreateException(sName); 556 return; // without creating 557 } 558 if (x != null) { 559 // bean already exists 560 JmriJOptionPane.showMessageDialog(addLogixNGFrame, Bundle.getMessage("LogixNGError1"), 561 Bundle.getMessage("ErrorTitle"), // NOI18N 562 JmriJOptionPane.ERROR_MESSAGE); 563 return; 564 } 565 if (!checkLogixNGUserName(uName)) { 566 return; 567 } 568 // Create the new bean 569 _curNamedBean = createBean(sName, uName); 570 if (_curNamedBean == null) { 571 // should never get here unless there is an assignment conflict 572 log.error("Failure to create bean with System Name: {}", sName); // NOI18N 573 return; 574 } 575 } 576 cancelAddPressed(null); 577 // create the Edit bean Window 578 editPressed(sName); 579 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> { 580 prefMgr.setCheckboxPreferenceState(systemNameAuto, _autoSystemName.isSelected()); 581 }); 582 } 583 584 void handleCreateException(String sysName) { 585 JmriJOptionPane.showMessageDialog(addLogixNGFrame, 586 Bundle.getMessage("ErrorLogixAddFailed", sysName), // NOI18N 587 Bundle.getMessage("ErrorTitle"), // NOI18N 588 JmriJOptionPane.ERROR_MESSAGE); 589 } 590 591 // ------------ Methods for Edit bean Pane ------------ 592 593 /** 594 * Respond to the Edit button pressed in LogixNG table. 595 * 596 * @param sName system name of LogixNG to be edited 597 */ 598 void editPressed(String sName) { 599 _curNamedBean = getManager().getBySystemName(sName); 600 if (!checkFlags(sName)) { 601 return; 602 } 603 604 // Create a new bean edit view, add the listener. 605 _editor = getEditor(m, sName); 606 607 if (_editor == null) return; // Editor not implemented yet for LogixNG Tables 608 609 _inEditMode = true; 610 611 _editor.addEditorEventListener((data) -> { 612 String lgxName = sName; 613 data.forEach((key, value) -> { 614 if (key.equals("Finish")) { // NOI18N 615 _editor = null; 616 _inEditMode = false; 617 f.setVisible(true); 618 } else if (key.equals("Delete")) { // NOI18N 619 _inEditMode = false; 620 deletePressed(value); 621 } else if (key.equals("chgUname")) { // NOI18N 622 E x = getManager().getBySystemName(lgxName); 623 if (x == null) { 624 log.error("Found no logixNG for name {} when changing user name (2)", lgxName); 625 return; 626 } 627 x.setUserName(value); 628 m.fireTableDataChanged(); 629 } 630 }); 631 }); 632 } 633 634 /** 635 * Display reminder to save. 636 */ 637 void showSaveReminder() { 638 if (_showReminder && !_checkEnabled) { 639 if (InstanceManager.getNullableDefault(jmri.UserPreferencesManager.class) != null) { 640 InstanceManager.getDefault(jmri.UserPreferencesManager.class). 641 showInfoMessage(Bundle.getMessage("ReminderTitle"), Bundle.getMessage("ReminderSaveString", Bundle.getMessage("MenuItemLogixNGTable")), // NOI18N 642 getClassName(), 643 "remindSaveLogix"); // NOI18N 644 } 645 } 646 } 647 648 @Override 649 public void setMessagePreferencesDetails() { 650 HashMap<Integer, String> options = new HashMap<>(3); 651 options.put(0x00, Bundle.getMessage("DeleteAsk")); // NOI18N 652 options.put(0x01, Bundle.getMessage("DeleteNever")); // NOI18N 653 options.put(0x02, Bundle.getMessage("DeleteAlways")); // NOI18N 654 jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class).setMessageItemDetails(getClassName(), "delete", Bundle.getMessage("DeleteLogixNG"), options, 0x00); // NOI18N 655 jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails(getClassName(), "remindSaveLogixNG", Bundle.getMessage("HideSaveReminder")); // NOI18N 656 super.setMessagePreferencesDetails(); 657 } 658 659 /** 660 * Respond to the Delete combo selection bean window delete request. 661 * 662 * @param sName system name of bean to be deleted 663 */ 664 void deletePressed(String sName) { 665 if (!checkFlags(sName)) { 666 return; 667 } 668 669 final E x = getManager().getBySystemName(sName); 670 671 if (x == null) return; // This should never happen 672 673 deleteBean.delete(x, hasChildren(x), (t)->{deleteBean(t);}, 674 (t,list)->{getListenerRefsIncludingChildren(t,list);}, 675 getClassName()); 676 } 677 678 /** 679 * Respond to the Execute combo selection bean window execute request. 680 * 681 * @param sName system name of bean to be deleted 682 */ 683 void executePressed(String sName) { 684 final E x = getManager().getBySystemName(sName); 685 686 if (x == null) return; // This should never happen 687 688 execute(x); 689 } 690 691 @Override 692 public String getClassDescription() { 693 return Bundle.getMessage("TitleLogixNGTable"); // NOI18N 694 } 695 696 @Override 697 protected String getClassName() { 698 // The class that is returned must have a default constructor, 699 // a constructor with no parameters. 700 return jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName(); 701 } 702 703// ------------ Methods for Conditional Browser Window ------------ 704 /** 705 * Respond to the Browse button pressed in bean table. 706 * 707 * @param sName The selected bean system name 708 */ 709 void browserPressed(String sName) { 710 // bean was found, create the window 711 _curNamedBean = getManager().getBySystemName(sName); 712 getPrintTreeSettings(); 713 makeBrowserWindow(); 714 } 715 716 void getPrintTreeSettings() { 717 // Set options 718 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> { 719 _printTreeSettings._printLineNumbers = prefMgr.getSimplePreferenceState(PRINT_LINE_NUMBERS_OPTION); 720 _printTreeSettings._printErrorHandling = prefMgr.getSimplePreferenceState(PRINT_ERROR_HANDLING_OPTION); 721 _printTreeSettings._printNotConnectedSockets = prefMgr.getSimplePreferenceState(PRINT_NOT_CONNECTED_OPTION); 722 _printTreeSettings._printLocalVariables = prefMgr.getSimplePreferenceState(PRINT_LOCAL_VARIABLES_OPTION); 723 _printTreeSettings._printSystemNames = prefMgr.getSimplePreferenceState(PRINT_SYSTEM_NAMES_OPTION); 724 }); 725 } 726 727 /** 728 * Update text in the browser window. 729 */ 730 void updateBrowserText() { 731 if (_textContent != null) { 732 _textContent.setText(getBeanText(_curNamedBean)); 733 } 734 } 735 736 /** 737 * Create and initialize the conditionalNGs browser window. 738 */ 739 void makeBrowserWindow() { 740 JmriJFrame condBrowserFrame = new JmriJFrame(this.getBrowserTitle(), false, true); 741 742 condBrowserFrame.addWindowListener(new WindowAdapter() { 743 @Override 744 public void windowClosed(WindowEvent e) { 745 _textContent = null; 746 } 747 }); 748 749 Container contentPane = condBrowserFrame.getContentPane(); 750 contentPane.setLayout(new BorderLayout()); 751 752 // bean header information 753 JPanel topPanel = new JPanel(); 754 String tStr = Bundle.getMessage("BrowserLogixNG") + " " + _curNamedBean.getSystemName() + " " // NOI18N 755 + _curNamedBean.getUserName() + " " 756 + (isEnabled(_curNamedBean) 757 ? Bundle.getMessage("BrowserEnabled") // NOI18N 758 : Bundle.getMessage("BrowserDisabled")); // NOI18N 759 topPanel.add(new JLabel(tStr)); 760 contentPane.add(topPanel, BorderLayout.NORTH); 761 762 // Build the conditionalNGs listing 763 _textContent = new JTextArea(this.getBeanText(_curNamedBean)); 764 if (browseMonoSpace()) { 765 _textContent.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 766 } 767 JScrollPane scrollPane = new JScrollPane(_textContent); 768 contentPane.add(scrollPane); 769 770 JPanel bottomPanel = new JPanel(); 771 bottomPanel.setLayout(new BorderLayout()); 772 JButton helpBrowse = new JButton(Bundle.getMessage("MenuHelp")); // NOI18N 773 bottomPanel.add(helpBrowse, BorderLayout.WEST); 774 helpBrowse.addActionListener((ActionEvent e) -> { 775 JmriJOptionPane.showMessageDialog(condBrowserFrame, 776 Bundle.getMessage("LogixNG_Browse_HelpText"), // NOI18N 777 Bundle.getMessage("BrowserHelpTitle"), // NOI18N 778 JmriJOptionPane.INFORMATION_MESSAGE); 779 }); 780 781 JPanel settingsPanel = getSettingsPanel(); 782 bottomPanel.add(settingsPanel, BorderLayout.CENTER); 783 784 JButton saveBrowse = new JButton(Bundle.getMessage("BrowserSaveButton")); // NOI18N 785 saveBrowse.setToolTipText(Bundle.getMessage("BrowserSaveButtonHint")); // NOI18N 786 bottomPanel.add(saveBrowse, BorderLayout.EAST); 787 saveBrowse.addActionListener((ActionEvent e) -> { 788 saveBrowserPressed(); 789 }); 790 contentPane.add(bottomPanel, BorderLayout.SOUTH); 791 792 condBrowserFrame.pack(); 793 condBrowserFrame.setVisible(true); 794 } // makeBrowserWindow 795 796 JFileChooser userFileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath()); 797 798 /** 799 * Save the bean browser window content to a text file. 800 */ 801 void saveBrowserPressed() { 802 userFileChooser.setApproveButtonText(Bundle.getMessage("BrowserSaveDialogApprove")); // NOI18N 803 userFileChooser.setDialogTitle(Bundle.getMessage("BrowserSaveDialogTitle")); // NOI18N 804 userFileChooser.rescanCurrentDirectory(); 805 // Default to logixNG system name.txt 806 String suggestedFileName = _curNamedBean.getSystemName().replace(':', '_') + ".txt"; 807 userFileChooser.setSelectedFile(new File(suggestedFileName)); // NOI18N 808 int retVal = userFileChooser.showSaveDialog(null); 809 if (retVal != JFileChooser.APPROVE_OPTION) { 810 log.debug("Save browser content stopped, no file selected"); // NOI18N 811 return; // give up if no file selected or cancel pressed 812 } 813 File file = userFileChooser.getSelectedFile(); 814 log.debug("Save browser content to '{}'", file); // NOI18N 815 816 if (file.exists()) { 817 Object[] options = {Bundle.getMessage("BrowserSaveDuplicateReplace"), // NOI18N 818 Bundle.getMessage("BrowserSaveDuplicateAppend"), // NOI18N 819 Bundle.getMessage("ButtonCancel")}; // NOI18N 820 int selectedOption = JmriJOptionPane.showOptionDialog(null, 821 Bundle.getMessage("BrowserSaveDuplicatePrompt", file.getName()), // NOI18N 822 Bundle.getMessage("BrowserSaveDuplicateTitle"), // NOI18N 823 JmriJOptionPane.DEFAULT_OPTION, 824 JmriJOptionPane.WARNING_MESSAGE, 825 null, options, options[0]); 826 if (selectedOption == 2 || selectedOption == -1) { 827 log.debug("Save browser content stopped, file replace/append cancelled"); // NOI18N 828 return; // Cancel selected or dialog box closed 829 } 830 if (selectedOption == 0) { 831 FileUtil.delete(file); // Replace selected 832 } 833 } 834 835 // Create the file content 836 String tStr = Bundle.getMessage("BrowserLogixNG") + " " + _curNamedBean.getSystemName() + " " // NOI18N 837 + _curNamedBean.getUserName() + " " 838 + (isEnabled(_curNamedBean) 839 ? Bundle.getMessage("BrowserEnabled") // NOI18N 840 : Bundle.getMessage("BrowserDisabled")); // NOI18N 841 try { 842 // Add bean Header inforation first 843 FileUtil.appendTextToFile(file, tStr); 844 FileUtil.appendTextToFile(file, "-".repeat(tStr.length())); 845 FileUtil.appendTextToFile(file, ""); 846 FileUtil.appendTextToFile(file, _textContent.getText()); 847 } catch (IOException e) { 848 log.error("Unable to write browser content to '{}'", file, e); // NOI18N 849 } 850 } 851 852 protected JPanel getSettingsPanel() { 853 JPanel checkBoxPanel = new JPanel(); 854 855 JCheckBox printLineNumbers = new JCheckBox(Bundle.getMessage("LogixNG_Browse_PrintLineNumbers")); 856 printLineNumbers.setSelected(_printTreeSettings._printLineNumbers); 857 printLineNumbers.addChangeListener((event) -> { 858 if (_printTreeSettings._printLineNumbers != printLineNumbers.isSelected()) { 859 _printTreeSettings._printLineNumbers = printLineNumbers.isSelected(); 860 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> { 861 prefMgr.setSimplePreferenceState(PRINT_LINE_NUMBERS_OPTION, printLineNumbers.isSelected()); 862 }); 863 updateBrowserText(); 864 } 865 }); 866 867 JCheckBox printErrorHandling = new JCheckBox(Bundle.getMessage("LogixNG_Browse_PrintErrorHandling")); 868 printErrorHandling.setSelected(_printTreeSettings._printErrorHandling); 869 printErrorHandling.addChangeListener((event) -> { 870 if (_printTreeSettings._printErrorHandling != printErrorHandling.isSelected()) { 871 _printTreeSettings._printErrorHandling = printErrorHandling.isSelected(); 872 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> { 873 prefMgr.setSimplePreferenceState(PRINT_ERROR_HANDLING_OPTION, printErrorHandling.isSelected()); 874 }); 875 updateBrowserText(); 876 } 877 }); 878 879 JCheckBox printNotConnectedSockets = new JCheckBox(Bundle.getMessage("LogixNG_Browse_PrintNotConnectedSocket")); 880 printNotConnectedSockets.setSelected(_printTreeSettings._printNotConnectedSockets); 881 printNotConnectedSockets.addChangeListener((event) -> { 882 if (_printTreeSettings._printNotConnectedSockets != printNotConnectedSockets.isSelected()) { 883 _printTreeSettings._printNotConnectedSockets = printNotConnectedSockets.isSelected(); 884 updateBrowserText(); 885 InstanceManager.getOptionalDefault(jmri.UserPreferencesManager.class).ifPresent((prefMgr) -> { 886 prefMgr.setSimplePreferenceState(PRINT_NOT_CONNECTED_OPTION, printNotConnectedSockets.isSelected()); 887 }); 888 } 889 }); 890 891 JCheckBox printLocalVariables = new JCheckBox(Bundle.getMessage("LogixNG_Browse_PrintLocalVariables")); 892 printLocalVariables.setSelected(_printTreeSettings._printLocalVariables); 893 printLocalVariables.addChangeListener((event) -> { 894 if (_printTreeSettings._printLocalVariables != printLocalVariables.isSelected()) { 895 _printTreeSettings._printLocalVariables = printLocalVariables.isSelected(); 896 updateBrowserText(); 897 InstanceManager.getOptionalDefault(jmri.UserPreferencesManager.class).ifPresent((prefMgr) -> { 898 prefMgr.setSimplePreferenceState(PRINT_LOCAL_VARIABLES_OPTION, printLocalVariables.isSelected()); 899 }); 900 } 901 }); 902 903 JCheckBox printSystemNames = new JCheckBox(Bundle.getMessage("LogixNG_Browse_PrintSystemNames")); 904 printSystemNames.setSelected(_printTreeSettings._printSystemNames); 905 printSystemNames.addChangeListener((event) -> { 906 if (_printTreeSettings._printSystemNames != printSystemNames.isSelected()) { 907 _printTreeSettings._printSystemNames = printSystemNames.isSelected(); 908 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> { 909 prefMgr.setSimplePreferenceState(PRINT_SYSTEM_NAMES_OPTION, printSystemNames.isSelected()); 910 }); 911 updateBrowserText(); 912 } 913 }); 914 915 if (this instanceof LogixNGTableAction || this instanceof LogixNGModuleTableAction) { 916 checkBoxPanel.add(printLineNumbers); 917 checkBoxPanel.add(printErrorHandling); 918 checkBoxPanel.add(printNotConnectedSockets); 919 checkBoxPanel.add(printLocalVariables); 920 checkBoxPanel.add(printSystemNames); 921 } 922 923 return checkBoxPanel; 924 } 925 926 927 928 protected class TableModel extends BeanTableDataModel<E> { 929 930 // overlay the state column with the edit column 931 static public final int ENABLECOL = VALUECOL; 932 static public final int EDITCOL = DELETECOL; 933 protected String enabledString = Bundle.getMessage("ColumnHeadEnabled"); // NOI18N 934 935 @Override 936 public String getColumnName(int col) { 937 if (col == EDITCOL) { 938 return Bundle.getMessage("ColumnHeadMenu"); // This makes it easier to test the table 939 } 940 if (col == ENABLECOL) { 941 return enabledString; 942 } 943 return super.getColumnName(col); 944 } 945 946 @Override 947 public Class<?> getColumnClass(int col) { 948 if (col == EDITCOL) { 949 return String.class; 950 } 951 if (col == ENABLECOL) { 952 return Boolean.class; 953 } 954 return super.getColumnClass(col); 955 } 956 957 @Override 958 public int getPreferredWidth(int col) { 959 // override default value for SystemName and UserName columns 960 if (col == SYSNAMECOL) { 961 return new JTextField(12).getPreferredSize().width; 962 } 963 if (col == USERNAMECOL) { 964 return new JTextField(17).getPreferredSize().width; 965 } 966 if (col == EDITCOL) { 967 return new JTextField(12).getPreferredSize().width; 968 } 969 if (col == ENABLECOL) { 970 return new JTextField(5).getPreferredSize().width; 971 } 972 return super.getPreferredWidth(col); 973 } 974 975 @Override 976 public boolean isCellEditable(int row, int col) { 977 if (col == EDITCOL) { 978 return true; 979 } 980 if (col == ENABLECOL) { 981 return true; 982 } 983 return super.isCellEditable(row, col); 984 } 985 986 @SuppressWarnings("unchecked") // Unchecked cast from Object to E 987 @Override 988 public Object getValueAt(int row, int col) { 989 if (col == EDITCOL) { 990 return Bundle.getMessage("ButtonSelect"); // NOI18N 991 } else if (col == ENABLECOL) { 992 E x = (E) getValueAt(row, SYSNAMECOL); 993 if (x == null) { 994 return null; 995 } 996 return isEnabled(x); 997 } else { 998 return super.getValueAt(row, col); 999 } 1000 } 1001 1002 @SuppressWarnings("unchecked") // Unchecked cast from Object to E 1003 @Override 1004 public void setValueAt(Object value, int row, int col) { 1005 if (col == EDITCOL) { 1006 // set up to edit 1007 String sName = ((NamedBean) getValueAt(row, SYSNAMECOL)).getSystemName(); 1008 if (Bundle.getMessage("ButtonEdit").equals(value)) { // NOI18N 1009 editPressed(sName); 1010 1011 } else if (Bundle.getMessage("BrowserButton").equals(value)) { // NOI18N 1012 conditionalRowNumber = row; 1013 browserPressed(sName); 1014 1015 } else if (Bundle.getMessage("ButtonCopy").equals(value)) { // NOI18N 1016 copyPressed(sName); 1017 1018 } else if (Bundle.getMessage("ButtonDelete").equals(value)) { // NOI18N 1019 deletePressed(sName); 1020 1021 } else if (Bundle.getMessage("LogixNG_ButtonExecute").equals(value)) { // NOI18N 1022 executePressed(sName); 1023 } 1024 } else if (col == ENABLECOL) { 1025 // alternate 1026 E x = (E) getValueAt(row, SYSNAMECOL); 1027 boolean v = isEnabled(x); 1028 setEnabled(x, !v); 1029 } else { 1030 super.setValueAt(value, row, col); 1031 } 1032 } 1033 1034 /** 1035 * Delete the bean after all the checking has been done. 1036 * <p> 1037 * Deletes the NamedBean. 1038 * 1039 * @param bean of the NamedBean to delete 1040 */ 1041 @Override 1042 protected void doDelete(E bean) { 1043 // delete the LogixNG 1044 AbstractLogixNGTableAction.this.deleteBean(bean); 1045 } 1046 1047 @Override 1048 protected boolean matchPropertyName(java.beans.PropertyChangeEvent e) { 1049 if (e.getPropertyName().equals(enabledString)) { 1050 return true; 1051 } 1052 return super.matchPropertyName(e); 1053 } 1054 1055 @Override 1056 public Manager<E> getManager() { 1057 return AbstractLogixNGTableAction.this.getManager(); 1058 } 1059 1060 @Override 1061 public E getBySystemName(String name) { 1062 return AbstractLogixNGTableAction.this.getManager().getBySystemName(name); 1063 } 1064 1065 @Override 1066 public E getByUserName(String name) { 1067 return AbstractLogixNGTableAction.this.getManager().getByUserName(name); 1068 } 1069 1070 @Override 1071 protected String getMasterClassName() { 1072 return getClassName(); 1073 } 1074 1075 @Override 1076 public void configureTable(JTable table) { 1077 table.setDefaultRenderer(Boolean.class, new EnablingCheckboxRenderer()); 1078 table.setDefaultRenderer(JComboBox.class, new jmri.jmrit.symbolicprog.ValueRenderer()); 1079 table.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor()); 1080 if (!(getManager() instanceof jmri.jmrit.logixng.LogixNG_Manager)) { 1081 table.getColumnModel().getColumn(2).setMinWidth(0); 1082 table.getColumnModel().getColumn(2).setMaxWidth(0); 1083 } 1084 super.configureTable(table); 1085 } 1086 1087 /** 1088 * Replace delete button with comboBox to edit/delete/copy/select NamedBean. 1089 * 1090 * @param table name of the NamedBean JTable holding the column 1091 */ 1092 @Override 1093 protected void configDeleteColumn(JTable table) { 1094 JComboBox<String> editCombo = new JComboBox<>(); 1095 editCombo.addItem(Bundle.getMessage("ButtonSelect")); // NOI18N 1096 if (isEditSupported()) editCombo.addItem(Bundle.getMessage("ButtonEdit")); // NOI18N 1097 editCombo.addItem(Bundle.getMessage("BrowserButton")); // NOI18N 1098 if (isCopyBeanSupported()) editCombo.addItem(Bundle.getMessage("ButtonCopy")); // NOI18N 1099 editCombo.addItem(Bundle.getMessage("ButtonDelete")); // NOI18N 1100 if (isExecuteSupported()) editCombo.addItem(Bundle.getMessage("LogixNG_ButtonExecute")); // NOI18N 1101 TableColumn col = table.getColumnModel().getColumn(BeanTableDataModel.DELETECOL); 1102 col.setCellEditor(new DefaultCellEditor(editCombo)); 1103 } 1104 1105 // Not needed - here for interface compatibility 1106 @Override 1107 public void clickOn(NamedBean t) { 1108 } 1109 1110 @Override 1111 public String getValue(String s) { 1112 return ""; 1113 } 1114 1115 @Override 1116 protected String getBeanType() { 1117// return Bundle.getMessage("BeanNameLogix"); // NOI18N 1118 return rbx.getString("BeanNameLogixNG"); // NOI18N 1119 } 1120 } 1121 1122 1123 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractLogixNGTableAction.class); 1124 1125}