001package jmri.jmrit.conditional; 002 003import java.awt.Component; 004import java.awt.Container; 005import java.awt.event.ActionEvent; 006import java.awt.event.ActionListener; 007import java.util.ArrayList; 008import java.util.EventListener; 009import java.util.HashMap; 010import java.util.List; 011import java.util.TreeSet; 012 013import javax.annotation.CheckForNull; 014import javax.annotation.Nonnull; 015 016import javax.swing.JFrame; 017import javax.swing.JTabbedPane; 018import javax.swing.JTable; 019import javax.swing.JTextField; 020import javax.swing.event.ListSelectionEvent; 021import javax.swing.event.ListSelectionListener; 022 023import jmri.Audio; 024import jmri.Conditional; 025import jmri.ConditionalManager; 026import jmri.ConditionalVariable; 027import jmri.InstanceManager; 028import jmri.Light; 029import jmri.LightManager; 030import jmri.Logix; 031import jmri.LogixManager; 032import jmri.Memory; 033import jmri.MemoryManager; 034import jmri.NamedBean; 035import jmri.Route; 036import jmri.Sensor; 037import jmri.SensorManager; 038import jmri.SignalHead; 039import jmri.SignalHeadManager; 040import jmri.SignalMast; 041import jmri.SignalMastManager; 042import jmri.Turnout; 043import jmri.TurnoutManager; 044import jmri.NamedBean.DisplayOptions; 045import jmri.jmrit.beantable.LRouteTableAction; 046import jmri.jmrit.entryexit.DestinationPoints; 047import jmri.jmrit.entryexit.EntryExitPairs; 048import jmri.jmrit.logix.OBlock; 049import jmri.jmrit.logix.OBlockManager; 050import jmri.jmrit.logix.Warrant; 051import jmri.jmrit.logix.WarrantManager; 052import jmri.jmrit.picker.PickFrame; 053import jmri.jmrit.picker.PickListModel; 054import jmri.jmrit.picker.PickSinglePanel; 055import jmri.swing.NamedBeanComboBox; 056import jmri.util.JmriJFrame; 057import jmri.util.swing.JComboBoxUtil; 058import jmri.util.swing.JmriJOptionPane; 059 060/** 061 * This is the base class for the Conditional edit view classes. Contains shared 062 * variables and methods. 063 * 064 * @author Dave Sand copyright (c) 2017 065 */ 066public class ConditionalEditBase { 067 068 /** 069 * Set the Logix and Conditional managers and set the selection mode. 070 * 071 * @param sName the Logix system name being edited 072 */ 073 public ConditionalEditBase(String sName) { 074// _logixManager = InstanceManager.getNullableDefault(jmri.LogixManager.class); 075// _conditionalManager = InstanceManager.getNullableDefault(jmri.ConditionalManager.class); 076 _logixManager = InstanceManager.getDefault(jmri.LogixManager.class); 077 _conditionalManager = InstanceManager.getDefault(jmri.ConditionalManager.class); 078 _curLogix = _logixManager.getBySystemName(sName); 079 loadSelectionMode(); 080 } 081 082 public ConditionalEditBase() { 083 } 084 085 // ------------ variable definitions ------------ 086 ConditionalManager _conditionalManager = null; 087 LogixManager _logixManager = null; 088 Logix _curLogix = null; 089 JmriJFrame _editLogixFrame = null; 090 091 boolean _inEditMode = false; 092 093 boolean _showReminder = false; 094 boolean _suppressReminder = false; 095 boolean _suppressIndirectRef = false; 096 private boolean _checkEnabled = jmri.InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).isStoreCheckEnabled(); 097 098 /** 099 * Input selection names. 100 * 101 * @since 4.7.3 102 */ 103 public enum SelectionMode { 104 /** 105 * Use the traditional text field, with the tabbed Pick List available 106 * for drag-n-drop 107 */ 108 USEMULTI, 109 /** 110 * Use the traditional text field, but with a single Pick List that 111 * responds with a click 112 */ 113 USESINGLE, 114 /** 115 * Use combo boxes to select names instead of a text field. 116 */ 117 USECOMBO; 118 } 119 SelectionMode _selectionMode; 120 121 /** 122 * Get the saved mode selection, default to the tranditional tabbed pick 123 * list. 124 * <p> 125 * During the menu build process, the corresponding menu item is set to 126 * selected. 127 * 128 * @since 4.7.3 129 */ 130 void loadSelectionMode() { 131 Object modeName = InstanceManager.getDefault(jmri.UserPreferencesManager.class).getProperty("jmri.jmrit.beantable.LogixTableAction", "Selection Mode"); // NOI18N 132 if (modeName == null) { 133 _selectionMode = SelectionMode.USEMULTI; 134 } else { 135 String currentMode = (String) modeName; 136 switch (currentMode) { 137 case "USEMULTI": // NOI18N 138 _selectionMode = SelectionMode.USEMULTI; 139 break; 140 case "USESINGLE": // NOI18N 141 _selectionMode = SelectionMode.USESINGLE; 142 break; 143 case "USECOMBO": // NOI18N 144 _selectionMode = SelectionMode.USECOMBO; 145 break; 146 default: 147 log.warn("Invalid Logix conditional selection mode value, '{}', returned", currentMode); // NOI18N 148 _selectionMode = SelectionMode.USEMULTI; 149 } 150 } 151 } 152 153 // ------------ PickList components ------------ 154 JTable _pickTable = null; // Current pick table 155 JTabbedPane _pickTabPane = null; // The tabbed panel for the pick table 156 PickFrame _pickTables; 157 158 JFrame _pickSingleFrame = null; 159 PickSingleListener _pickListener = null; 160 161 // ------------ Logix Notifications ------------ 162 // The Conditional views support some direct changes to the parent logix. 163 // This custom event is used to notify the parent Logix that changes are requested. 164 // When the event occurs, the parent Logix can retrieve the necessary information 165 // to carry out the actions. 166 // 167 // 1) Notify the calling Logix that the Logix user name has been changed. 168 // 2) Notify the calling Logix that the conditional view is closing 169 // 3) Notify the calling Logix that it is to be deleted 170 /** 171 * Create a custom listener event. 172 */ 173 public interface LogixEventListener extends EventListener { 174 175 void logixEventOccurred(); 176 } 177 178 /** 179 * Maintain a list of listeners -- normally only one. 180 */ 181 List<LogixEventListener> listenerList = new ArrayList<>(); 182 183 /** 184 * This contains a list of commands to be processed by the listener 185 * recipient. 186 */ 187 public HashMap<String, String> logixData = new HashMap<>(); 188 189 /** 190 * Add a listener. 191 * 192 * @param listener The recipient 193 */ 194 public void addLogixEventListener(LogixEventListener listener) { 195 listenerList.add(listener); 196 } 197 198 /** 199 * Remove a listener -- not used. 200 * 201 * @param listener The recipient 202 */ 203 public void removeLogixEventListener(LogixEventListener listener) { 204 listenerList.remove(listener); 205 } 206 207 /** 208 * Notify the listeners to check for new data. 209 */ 210 void fireLogixEvent() { 211 for (LogixEventListener l : listenerList) { 212 l.logixEventOccurred(); 213 } 214 } 215 216 // ------------ Antecedent Methods ------------ 217 218 /** 219 * Create an antecedent string based on the current variables 220 * <p> 221 * The antecedent consists of all of the variables "in order" 222 * combined with the current operator. 223 * @since 4.11.5 224 * @param variableList The current variable list 225 * @return the resulting antecedent string 226 */ 227 String makeAntecedent(List<ConditionalVariable> variableList) { 228 StringBuilder antecedent = new StringBuilder(64); 229 if (!variableList.isEmpty()) { 230 String row = "R"; // NOI18N 231 if (variableList.get(0).isNegated()) { 232 antecedent.append("not "); 233 } 234 antecedent.append(row + "1"); 235 for (int i = 1; i < variableList.size(); i++) { 236 ConditionalVariable variable = variableList.get(i); 237 switch (variable.getOpern()) { 238 case AND: 239 antecedent.append(" and "); 240 break; 241 case OR: 242 antecedent.append(" or "); 243 break; 244 default: 245 break; 246 } 247 if (variable.isNegated()) { 248 antecedent = antecedent.append("not "); 249 } 250 antecedent.append(row); 251 antecedent.append(i + 1); 252 } 253 } 254 return antecedent.toString(); 255 } 256 257 /** 258 * Add a variable R# entry to the antecedent string. 259 * If not the first one, include <strong>and</strong> or <strong>or</strong> depending on the logic type 260 * @since 4.11.5 261 * @param logicType The current logic type. 262 * @param varListSize The current size of the variable list. 263 * @param antecedent The current antecedent 264 * @return an extended antecedent 265 */ 266 String appendToAntecedent(Conditional.AntecedentOperator logicType, int varListSize, String antecedent) { 267 if (varListSize > 1) { 268 if (logicType == Conditional.AntecedentOperator.ALL_OR) { 269 antecedent = antecedent + " or "; // NOI18N 270 } else { 271 antecedent = antecedent + " and "; // NOI18N 272 } 273 } 274 return antecedent + "R" + varListSize; // NOI18N 275 } 276 277 /** 278 * Check the antecedent and logic type. 279 * <p> 280 * The antecedent text is translated and verified. A new one is created if necessary. 281 * @since 4.11.5 282 * @param logicType The current logic type. Types other than Mixed are ignored. 283 * @param antecedentText The proposed antecedent string using the local language. 284 * @param variableList The current variable list. 285 * @param curConditional The current conditional. 286 * @return false if antecedent can't be validated 287 */ 288 boolean validateAntecedent(Conditional.AntecedentOperator logicType, String antecedentText, List<ConditionalVariable> variableList, Conditional curConditional) { 289 if (logicType != Conditional.AntecedentOperator.MIXED 290 || LRouteTableAction.getLogixInitializer().equals(_curLogix.getSystemName()) 291 || antecedentText == null 292 || antecedentText.trim().length() == 0) { 293 return true; 294 } 295 296 String antecedent = translateAntecedent(antecedentText, true); 297 if (antecedent.length() > 0) { 298 String message = curConditional.validateAntecedent(antecedent, variableList); 299 if (message != null) { 300 JmriJOptionPane.showMessageDialog(_editLogixFrame, 301 message + Bundle.getMessage("ParseError8"), // NOI18N 302 Bundle.getMessage("ErrorTitle"), // NOI18N 303 JmriJOptionPane.ERROR_MESSAGE); 304 return false; 305 } 306 } 307 return true; 308 } 309 310 /** 311 * Translate an antecedent string between English and the current language 312 * as determined by the Bundle classes. 313 * <p> 314 * The property files have Logic??? keys for translating to the target language. 315 * @since 4.11.5 316 * @param antecedent The antecedent string which can either local or English 317 * @param isLocal True if the antecedent string has local words. 318 * @return the translated antecedent string. 319 */ 320 public static String translateAntecedent(String antecedent, boolean isLocal) { 321 if (antecedent == null) { 322 return null; 323 } 324 String oldAnd, oldOr, oldNot; 325 String newAnd, newOr, newNot; 326 if (isLocal) { 327 // To English 328 oldAnd = Bundle.getMessage("LogicAND").toLowerCase(); // NOI18N 329 oldOr = Bundle.getMessage("LogicOR").toLowerCase(); // NOI18N 330 oldNot = Bundle.getMessage("LogicNOT").toLowerCase(); // NOI18N 331 newAnd = "and"; // NOI18N 332 newOr = "or"; // NOI18N 333 newNot = "not"; // NOI18N 334 } else { 335 // From English 336 oldAnd = "and"; // NOI18N 337 oldOr = "or"; // NOI18N 338 oldNot = "not"; // NOI18N 339 newAnd = Bundle.getMessage("LogicAND").toLowerCase(); // NOI18N 340 newOr = Bundle.getMessage("LogicOR").toLowerCase(); // NOI18N 341 newNot = Bundle.getMessage("LogicNOT").toLowerCase(); // NOI18N 342 } 343 log.debug("translateAntecedent: before {}", antecedent); 344 antecedent = antecedent.replaceAll(oldAnd, newAnd); 345 antecedent = antecedent.replaceAll(oldOr, newOr); 346 antecedent = antecedent.replaceAll(oldNot, newNot); 347 log.debug("translateAntecedent: after {}", antecedent); 348 return antecedent; 349 } 350 351 // ------------ Shared Conditional Methods ------------ 352 353 /** 354 * Verify that the user name is not a duplicate for the selected Logix. 355 * 356 * @param uName is the user name to be checked 357 * @param logix is the Logix that is being updated 358 * @return true if the name is unique 359 */ 360 boolean checkConditionalUserName(String uName, Logix logix) { 361 if (uName != null && uName.length() > 0) { 362 Conditional p = _conditionalManager.getByUserName(logix, uName); 363 if (p != null) { 364 // Conditional with this user name already exists 365 log.error("Failure to update Conditional with Duplicate User Name: {}", uName); 366 JmriJOptionPane.showMessageDialog(_editLogixFrame, 367 Bundle.getMessage("Error10"), // NOI18N 368 Bundle.getMessage("ErrorTitle"), // NOI18N 369 JmriJOptionPane.ERROR_MESSAGE); 370 return false; 371 } 372 } // else return true; 373 return true; 374 } 375 376 /** 377 * Create a combo name box for Variable and Action name selection. 378 * 379 * @param itemType The selected variable or action type 380 * @return nameBox A combo box based on the item type 381 */ 382 NamedBeanComboBox<?> createNameBox(@Nonnull Conditional.ItemType itemType) { 383 NamedBeanComboBox<?> nameBox; 384 switch (itemType) { 385 case SENSOR: // 1 386 nameBox = new NamedBeanComboBox<Sensor>( 387 InstanceManager.getDefault(SensorManager.class), null, DisplayOptions.DISPLAYNAME); 388 break; 389 case TURNOUT: // 2 390 nameBox = new NamedBeanComboBox<Turnout>( 391 InstanceManager.getDefault(TurnoutManager.class), null, DisplayOptions.DISPLAYNAME); 392 break; 393 case LIGHT: // 3 394 nameBox = new NamedBeanComboBox<Light>( 395 InstanceManager.getDefault(LightManager.class), null, DisplayOptions.DISPLAYNAME); 396 break; 397 case SIGNALHEAD: // 4 398 nameBox = new NamedBeanComboBox<SignalHead>( 399 InstanceManager.getDefault(SignalHeadManager.class), null, DisplayOptions.DISPLAYNAME); 400 break; 401 case SIGNALMAST: // 5 402 nameBox = new NamedBeanComboBox<SignalMast>( 403 InstanceManager.getDefault(SignalMastManager.class), null, DisplayOptions.DISPLAYNAME); 404 break; 405 case MEMORY: // 6 406 nameBox = new NamedBeanComboBox<Memory>( 407 InstanceManager.getDefault(MemoryManager.class), null, DisplayOptions.DISPLAYNAME); 408 break; 409 case LOGIX: // 7 410 nameBox = new NamedBeanComboBox<Logix>( 411 InstanceManager.getDefault(LogixManager.class), null, DisplayOptions.DISPLAYNAME); 412 break; 413 case WARRANT: // 8 414 nameBox = new NamedBeanComboBox<Warrant>( 415 InstanceManager.getDefault(WarrantManager.class), null, DisplayOptions.DISPLAYNAME); 416 break; 417 case OBLOCK: // 10 418 nameBox = new NamedBeanComboBox<OBlock>( 419 InstanceManager.getDefault(OBlockManager.class), null, DisplayOptions.DISPLAYNAME); 420 break; 421 case ENTRYEXIT: // 11 422 nameBox = new NamedBeanComboBox<DestinationPoints>( 423 InstanceManager.getDefault(EntryExitPairs.class), null, DisplayOptions.DISPLAYNAME); 424 break; 425 case OTHER: // 14 426 nameBox = new NamedBeanComboBox<Route>( 427 InstanceManager.getDefault(jmri.RouteManager.class), null, DisplayOptions.DISPLAYNAME); 428 break; 429 default: 430 return null; // Skip any other items. 431 } 432 nameBox.setAllowNull(true); 433 JComboBoxUtil.setupComboBoxMaxRows(nameBox); 434 return nameBox; 435 } 436 437 /** 438 * Listen for name combo box selection events. 439 * <p> 440 * When a combo box row is selected, the user/system name is copied to the 441 * Action or Variable name field. 442 * 443 * @since 4.7.3 444 */ 445 static class NameBoxListener implements ActionListener { 446 447 /** 448 * @param textField The target field object when an entry is selected 449 */ 450 NameBoxListener(JTextField textField) { 451 saveTextField = textField; 452 } 453 454 private final JTextField saveTextField; 455 456 @Override 457 public void actionPerformed(ActionEvent e) { 458 // Get the combo box and display name 459 Object src = e.getSource(); 460 if (!(src instanceof NamedBeanComboBox)) { 461 return; 462 } 463 NamedBeanComboBox<?> srcBox = (NamedBeanComboBox<?>) src; 464 String newName = srcBox.getSelectedItemDisplayName(); 465 466 log.debug("NameBoxListener: new name = '{}'", newName); // NOI18N 467 saveTextField.setText(newName); 468 } 469 } 470 471 // ------------ Single Pick List Table Methods ------------ 472 473 /** 474 * Create a single panel picklist JFrame for choosing action and variable 475 * names. 476 * 477 * @since 4.7.3 478 * @param itemType The selected variable or action type 479 * @param listener The listener to be assigned to the picklist 480 * @param actionType True if Action, false if Variable. 481 */ 482 void createSinglePanelPickList(Conditional.ItemType itemType, PickSingleListener listener, boolean actionType) { 483 if (_pickListener != null) { 484 Conditional.ItemType saveType = _pickListener.getItemType(); 485 if (saveType != itemType) { 486 // The type has changed, need to start over 487 closeSinglePanelPickList(); 488 } else { 489 // The pick list has already been created 490 return; 491 } 492 } 493 494 PickSinglePanel<?> pickSingle; 495 496 switch (itemType) { 497 case SENSOR: // 1 498 pickSingle = new PickSinglePanel<Sensor>(PickListModel.sensorPickModelInstance()); 499 break; 500 case TURNOUT: // 2 501 pickSingle = new PickSinglePanel<Turnout>(PickListModel.turnoutPickModelInstance()); 502 break; 503 case LIGHT: // 3 504 pickSingle = new PickSinglePanel<Light>(PickListModel.lightPickModelInstance()); 505 break; 506 case SIGNALHEAD: // 4 507 pickSingle = new PickSinglePanel<SignalHead>(PickListModel.signalHeadPickModelInstance()); 508 break; 509 case SIGNALMAST: // 5 510 pickSingle = new PickSinglePanel<SignalMast>(PickListModel.signalMastPickModelInstance()); 511 break; 512 case MEMORY: // 6 513 pickSingle = new PickSinglePanel<Memory>(PickListModel.memoryPickModelInstance()); 514 break; 515 case LOGIX: // 7 -- can be either Logix or Conditional 516 if (!actionType) { 517 // State Variable 518 return; 519 } 520 pickSingle = new PickSinglePanel<Logix>(PickListModel.logixPickModelInstance()); 521 break; 522 case WARRANT: // 8 523 pickSingle = new PickSinglePanel<Warrant>(PickListModel.warrantPickModelInstance()); 524 break; 525 case OBLOCK: // 10 526 pickSingle = new PickSinglePanel<OBlock>(PickListModel.oBlockPickModelInstance()); 527 break; 528 case ENTRYEXIT: // 11 529 pickSingle = new PickSinglePanel<jmri.jmrit.entryexit.DestinationPoints>(PickListModel.entryExitPickModelInstance()); 530 break; 531 default: 532 return; // Skip any other items. 533 } 534 535 // Create the JFrame 536 _pickSingleFrame = new JmriJFrame(Bundle.getMessage("SinglePickFrame")); // NOI18N 537 _pickSingleFrame.setContentPane(pickSingle); 538 _pickSingleFrame.pack(); 539 _pickSingleFrame.setVisible(true); 540 _pickSingleFrame.toFront(); 541 542 // Set the table selection listener 543 _pickListener = listener; 544 _pickTable = pickSingle.getTable(); 545 _pickTable.getSelectionModel().addListSelectionListener(_pickListener); 546 } 547 548 /** 549 * Close a single panel picklist JFrame and related items. 550 * 551 * @since 4.7.3 552 */ 553 void closeSinglePanelPickList() { 554 if (_pickSingleFrame != null) { 555 _pickSingleFrame.setVisible(false); 556 _pickSingleFrame.dispose(); 557 _pickSingleFrame = null; 558 _pickListener = null; 559 _pickTable = null; 560 } 561 } 562 563 /** 564 * Listen for Pick Single table click events. 565 * <p> 566 * When a table row is selected, the user/system name is copied to the 567 * Action or Variable name field. 568 * 569 * @since 4.7.3 570 */ 571 class PickSingleListener implements ListSelectionListener { 572 573 /** 574 * @param textField The target field object when an entry is selected 575 * @param itemType The current selected table type number 576 */ 577 PickSingleListener(JTextField textField, Conditional.ItemType itemType) { 578 saveItemType = itemType; 579 saveTextField = textField; 580 } 581 582 private final JTextField saveTextField; 583 private final Conditional.ItemType saveItemType; // Current table type 584 585 @Override 586 public void valueChanged(ListSelectionEvent e) { 587 int selectedRow = _pickTable.getSelectedRow(); 588 if (selectedRow >= 0) { 589 int selectedCol = _pickTable.getSelectedColumn(); 590 String newName = (String) _pickTable.getValueAt(selectedRow, selectedCol); 591 log.debug("Pick single panel row event: row = '{}', column = '{}', selected name = '{}'", 592 selectedRow, selectedCol, newName); 593 saveTextField.setText(newName); 594 } 595 } 596 597 public Conditional.ItemType getItemType() { 598 return saveItemType; 599 } 600 } 601 602 // ------------ Pick List Table Methods ------------ 603 604 /** 605 * Open a new drag-n-drop Pick List to drag Variable and Action names from 606 * to form Logix Conditionals. 607 */ 608 void openPickListTable() { 609 if (_pickTables == null) { 610 _pickTables = new jmri.jmrit.picker.PickFrame(Bundle.getMessage("TitlePickList")); // NOI18N 611 } else { 612 _pickTables.setVisible(true); 613 } 614 _pickTables.toFront(); 615 } 616 617 /** 618 * Hide the drag-n-drop Pick List if the last detail edit is closing. 619 */ 620 void hidePickListTable() { 621 if (_pickTables != null) { 622 _pickTables.setVisible(false); 623 } 624 } 625 626 /** 627 * Set the pick list tab based on the variable or action type. If there is 628 * not a corresponding tab, hide the picklist. 629 * 630 * @param curType is the current type 631 * @param actionType True if Action, false if Variable. 632 */ 633 void setPickListTab(Conditional.ItemType curType, boolean actionType) { 634 boolean tabSet = true; 635 if (_pickTables == null) { 636 return; 637 } 638 if (_pickTabPane == null) { 639 findPickListTabPane(_pickTables.getComponents(), 1); 640 } 641 if (_pickTabPane != null) { 642 // Convert variable/action type to the corresponding tab index 643 int tabIndex = 0; 644 switch (curType) { 645 case SENSOR: // 1 646 tabIndex = 1; 647 break; 648 case TURNOUT: // 2 649 tabIndex = 0; 650 break; 651 case LIGHT: // 3 652 tabIndex = 6; 653 break; 654 case SIGNALHEAD: // 4 655 tabIndex = 2; 656 break; 657 case SIGNALMAST: // 5 658 tabIndex = 3; 659 break; 660 case MEMORY: // 6 661 tabIndex = 4; 662 break; 663 case LOGIX: // 7 Conditional (Variable) or Logix (Action) 664 if (actionType) { 665 tabIndex = 10; 666 } else { 667 // State Variable 668 tabSet = false; 669 } 670 break; 671 case WARRANT: // 8 672 tabIndex = 7; 673 break; 674 case OBLOCK: // 10 675 tabIndex = 8; 676 break; 677 case ENTRYEXIT: // 11 678 tabIndex = 9; 679 break; 680 default: 681 // No tab found 682 tabSet = false; 683 } 684 if (tabSet) { 685 _pickTabPane.setSelectedIndex(tabIndex); 686 } 687 } 688 _pickTables.setVisible(tabSet); 689 } 690 691 /** 692 * Recursive search for the tab panel. 693 * 694 * @param compList The components for the current Level 695 * @param level The current level in the structure 696 */ 697 void findPickListTabPane(Component[] compList, int level) { 698 for (Component compItem : compList) { 699 // Safety catch 700 if (level > 10) { 701 log.warn("findPickListTabPane: safety breaker reached"); // NOI18N 702 return; 703 } 704 705 if (compItem instanceof JTabbedPane) { 706 _pickTabPane = (JTabbedPane) compItem; 707 } else { 708 int nextLevel = level + 1; 709 if ( compItem instanceof Container ) { 710 Container nextItem = (Container) compItem; 711 Component[] nextList = nextItem.getComponents(); 712 findPickListTabPane(nextList, nextLevel); 713 } else { 714 log.error("compItem {} is not a JTabbedPane, nor Container", compItem); 715 } 716 } 717 } 718 } 719 720 // ------------ Manage Conditional Reference map ------------ 721 722 /** 723 * Build a tree set from conditional references. 724 * 725 * @since 4.7.4 726 * @param varList The ConditionalVariable list that might contain 727 * conditional references 728 * @param treeSet A tree set to be built from the varList data 729 */ 730 void loadReferenceNames(List<ConditionalVariable> varList, TreeSet<String> treeSet) { 731 treeSet.clear(); 732 for (ConditionalVariable condVar : varList) { 733 if (condVar.getType() == Conditional.Type.CONDITIONAL_TRUE 734 || condVar.getType() == Conditional.Type.CONDITIONAL_FALSE) { 735 treeSet.add(condVar.getName()); 736 } 737 } 738 } 739 740 /** 741 * Check for conditional references. 742 * 743 * @since 4.7.4 744 * @param logixName The Logix under consideration 745 * @return true if no references 746 */ 747 boolean checkConditionalReferences(String logixName) { 748 Logix x = _logixManager.getLogix(logixName); 749 int numConditionals = x.getNumConditionals(); 750 for (int i = 0; i < numConditionals; i++) { 751 String csName = x.getConditionalByNumberOrder(i); 752 753 // If the conditional is a where used target, check scope 754 ArrayList<String> refList = InstanceManager.getDefault(jmri.ConditionalManager.class).getWhereUsed(csName); 755 if (refList != null) { 756 for (String refName : refList) { 757 Logix xRef = _conditionalManager.getParentLogix(refName); 758 if (xRef == null) { 759 log.error("Could not fetch Logix for ref: {}", refName); 760 continue; 761 } 762 String xsName = xRef.getSystemName(); 763 if (logixName.equals(xsName)) { 764 // Member of the same Logix 765 continue; 766 } 767 768 // External references have to be removed before the Logix can be deleted. 769 Conditional c = x.getConditional(csName); 770 Conditional cRef = xRef.getConditional(refName); 771 Object[] msgs = new Object[]{(c != null ? c.getUserName() : "NULL"), csName, 772 ( cRef != null ? cRef.getUserName() : "NULL"), refName, 773 xRef.getUserName(), xRef.getSystemName()}; 774 JmriJOptionPane.showMessageDialog(_editLogixFrame, 775 Bundle.getMessage("Error11", msgs), // NOI18N 776 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); // NOI18N 777 return false; 778 } 779 } 780 } 781 return true; 782 } 783 784 /** 785 * Update the conditional reference where used. 786 * <p> 787 * The difference between the saved target names and new target names is 788 * used to add/remove where used references. 789 * 790 * @since 4.7.4 791 * @param oldTargetNames The conditional target names before updating 792 * @param newTargetNames The conditional target names after updating 793 * @param refName The system name for the referencing conditional 794 */ 795 void updateWhereUsed(TreeSet<String> oldTargetNames, TreeSet<String> newTargetNames, String refName) { 796 TreeSet<String> deleteNames = new TreeSet<>(oldTargetNames); 797 deleteNames.removeAll(newTargetNames); 798 for (String deleteName : deleteNames) { 799 InstanceManager.getDefault(jmri.ConditionalManager.class).removeWhereUsed(deleteName, refName); 800 } 801 802 TreeSet<String> addNames = new TreeSet<>(newTargetNames); 803 addNames.removeAll(oldTargetNames); 804 for (String addName : addNames) { 805 InstanceManager.getDefault(jmri.ConditionalManager.class).addWhereUsed(addName, refName); 806 } 807 } 808 809 // ------------ Utility Methods - Data Validation ------------ 810 /** 811 * Display reminder to save. The class is set to LogixTableAction. 812 */ 813 void showSaveReminder() { 814 jmri.UserPreferencesManager upm = InstanceManager.getNullableDefault(jmri.UserPreferencesManager.class); 815 if ( _showReminder && !_checkEnabled && upm != null ) { 816 upm.showInfoMessage(Bundle.getMessage("ReminderTitle"), 817 Bundle.getMessage("ReminderSaveString", Bundle.getMessage("MenuItemLogixTable")), 818 "jmri.jmrit.beantable.LogixTableAction","remindSaveLogix"); 819 } 820 } 821 822 /** 823 * Check if String is an integer or references an integer. 824 * 825 * @param actionType Conditional action to check for, i.e. 826 * ACTION_SET_LIGHT_INTENSITY 827 * @param intReference string referencing a decimal for light intensity or 828 * the name of a memory 829 * @return true if either correct decimal format or a memory with the given 830 * name is present 831 */ 832 boolean validateIntensityReference(Conditional.Action actionType, @CheckForNull String intReference) { 833 if (intReference == null || intReference.trim().length() == 0) { 834 displayBadNumberReference(actionType); 835 return false; 836 } 837 try { 838 return validateIntensity(Integer.parseInt(intReference)); 839 } catch (NumberFormatException e) { 840 String intRef = intReference; 841 if (intReference.length() > 1 && intReference.charAt(0) == '@') { 842 intRef = intRef.substring(1); 843 } 844 if (!confirmIndirectMemory(intRef)) { 845 return false; 846 } 847 intRef = validateMemoryReference(intRef); 848 if (intRef != null) // memory named 'intReference' exists 849 { 850 Memory m = InstanceManager.memoryManagerInstance().getByUserName(intRef); 851 if (m == null) { 852 m = InstanceManager.memoryManagerInstance().getBySystemName(intRef); 853 } 854 try { 855 if (m == null || m.getValue() == null) { 856 throw new NumberFormatException(); 857 } 858 validateIntensity(Integer.parseInt((String) m.getValue())); 859 } catch (NumberFormatException ex) { 860 JmriJOptionPane.showMessageDialog(_editLogixFrame, 861 Bundle.getMessage("Error24", intReference), 862 Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE); 863 } 864 return true; // above is a warning to set memory correctly 865 } 866 displayBadNumberReference(actionType); 867 } 868 return false; 869 } 870 871 /** 872 * Check if text represents an integer is suitable for percentage w/o 873 * NumberFormatException. 874 * 875 * @param time value to use as light intensity percentage 876 * @return true if time is an integer in range 0 - 100 877 */ 878 boolean validateIntensity(int time) { 879 if (time < 0 || time > 100) { 880 JmriJOptionPane.showMessageDialog(_editLogixFrame, 881 Bundle.getMessage("Error38", time, Bundle.getMessage("Error42")), 882 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 883 return false; 884 } 885 return true; 886 } 887 888 /** 889 * Check if a string is decimal or references a decimal. 890 * 891 * @param actionType enum representing the Conditional action type being 892 * checked, i.e. ACTION_DELAYED_TURNOUT 893 * @param ref entry to check 894 * @return true if ref is itself a decimal or user will provide one from a 895 * Memory at run time 896 */ 897 boolean validateTimeReference(Conditional.Action actionType, @CheckForNull String ref) { 898 if (ref == null || ref.trim().length() == 0) { 899 displayBadNumberReference(actionType); 900 return false; 901 } 902 try { 903 return validateTime(actionType, Float.parseFloat(ref)); 904 // return true if ref is decimal within allowed range 905 } catch (NumberFormatException e) { 906 String memRef = ref; 907 if (ref.length() > 1 && ref.charAt(0) == '@') { 908 memRef = ref.substring(1); 909 } 910 if (!confirmIndirectMemory(memRef)) { 911 return false; 912 } 913 memRef = validateMemoryReference(memRef); 914 if (memRef != null) // memory named 'intReference' exists 915 { 916 Memory m = InstanceManager.memoryManagerInstance().getByUserName(memRef); 917 if (m == null) { 918 m = InstanceManager.memoryManagerInstance().getBySystemName(memRef); 919 } 920 try { 921 if (m == null || m.getValue() == null) { 922 throw new NumberFormatException(); 923 } 924 validateTime(actionType, Float.parseFloat((String) m.getValue())); 925 } catch (NumberFormatException ex) { 926 JmriJOptionPane.showMessageDialog(_editLogixFrame, 927 Bundle.getMessage("Error24", memRef), 928 Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE); 929 } 930 return true; // above is a warning to set memory correctly 931 } 932 displayBadNumberReference(actionType); 933 } 934 return false; 935 } 936 937 /** 938 * Range check time entry (assumes seconds). 939 * 940 * @param actionType integer representing the Conditional action type being 941 * checked, i.e. ACTION_DELAYED_TURNOUT 942 * @param time value to be checked 943 * @return false if time > 3600 (seconds) or too small 944 */ 945 boolean validateTime(Conditional.Action actionType, float time) { 946 float maxTime = 3600; // more than 1 hour 947 float minTime = 0.020f; 948 if (time < minTime || time > maxTime) { 949 String errorNum = " "; 950 switch (actionType) { 951 case DELAYED_TURNOUT: 952 errorNum = "Error39"; // NOI18N 953 break; 954 case RESET_DELAYED_TURNOUT: 955 errorNum = "Error41"; // NOI18N 956 break; 957 case DELAYED_SENSOR: 958 errorNum = "Error23"; // NOI18N 959 break; 960 case RESET_DELAYED_SENSOR: 961 errorNum = "Error27"; // NOI18N 962 break; 963 case SET_LIGHT_TRANSITION_TIME: 964 errorNum = "Error29"; // NOI18N 965 break; 966 default: 967 break; 968 } 969 JmriJOptionPane.showMessageDialog(_editLogixFrame, 970 Bundle.getMessage("Error38", time, Bundle.getMessage(errorNum)), 971 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 972 return false; 973 } 974 return true; 975 } 976 977 /** 978 * Display an error message to user when an invalid number is provided in 979 * Conditional setup. 980 * 981 * @param actionType integer representing the Conditional action type being 982 * checked, i.e. ACTION_DELAYED_TURNOUT 983 */ 984 void displayBadNumberReference(@Nonnull Conditional.Action actionType) { 985 String errorNum = " "; 986 switch (actionType) { 987 case DELAYED_TURNOUT: 988 errorNum = "Error39"; // NOI18N 989 break; 990 case RESET_DELAYED_TURNOUT: 991 errorNum = "Error41"; // NOI18N 992 break; 993 case DELAYED_SENSOR: 994 errorNum = "Error23"; // NOI18N 995 break; 996 case RESET_DELAYED_SENSOR: 997 errorNum = "Error27"; // NOI18N 998 break; 999 case SET_LIGHT_INTENSITY: 1000 JmriJOptionPane.showMessageDialog(_editLogixFrame, 1001 Bundle.getMessage("Error43"), // NOI18N 1002 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); // NOI18N 1003 return; 1004 case SET_LIGHT_TRANSITION_TIME: 1005 errorNum = "Error29"; // NOI18N 1006 break; 1007 default: 1008 log.warn("Unexpected action type {} in displayBadNumberReference", actionType); // NOI18N 1009 } 1010 JmriJOptionPane.showMessageDialog(_editLogixFrame, 1011 Bundle.getMessage("Error9", Bundle.getMessage(errorNum)), 1012 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); // NOI18N 1013 } 1014 1015 /** 1016 * Check Memory reference of text. 1017 * <p> 1018 * Show a message if not found. 1019 * 1020 * @param name the name to look for 1021 * @return the system or user name of the corresponding Memory, null if not 1022 * found 1023 */ 1024 @CheckForNull 1025 String validateMemoryReference(@CheckForNull String name) { 1026 Memory m = null; 1027 if (name != null) { 1028 if (name.length() > 0) { 1029 m = InstanceManager.memoryManagerInstance().getByUserName(name); 1030 if (m != null) { 1031 return name; 1032 } 1033 } 1034 m = InstanceManager.memoryManagerInstance().getBySystemName(name); 1035 } 1036 if (m == null) { 1037 messageInvalidActionItemName(name, "Memory"); // NOI18N 1038 return null; 1039 } 1040 return name; 1041 } 1042 1043 /** 1044 * Check if user will provide a valid item name in a Memory variable. 1045 * 1046 * @param memName Memory location to provide item name at run time 1047 * @return false if user replies No 1048 */ 1049 boolean confirmIndirectMemory(String memName) { 1050 if (!_suppressIndirectRef) { 1051 int response = JmriJOptionPane.showConfirmDialog(_editLogixFrame, 1052 Bundle.getMessage("ConfirmIndirectReference", memName, 1053 Bundle.getMessage("ButtonYes"), Bundle.getMessage("ButtonNo"), 1054 Bundle.getMessage("ButtonCancel")), // NOI18N 1055 Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_CANCEL_OPTION, // NOI18N 1056 JmriJOptionPane.QUESTION_MESSAGE); 1057 if (response == JmriJOptionPane.NO_OPTION || response == JmriJOptionPane.CLOSED_OPTION ) { 1058 return false; 1059 } else if (response == JmriJOptionPane.CANCEL_OPTION) { 1060 _suppressIndirectRef = true; 1061 } 1062 } 1063 return true; 1064 } 1065 1066 /** 1067 * Check if user OK's the use of an item as both an action and 1068 * a state variable. 1069 * 1070 * @param actionName name of ConditionalAction 1071 * @param variableName name of ConditionalVariable 1072 * @return false if user replies No 1073 */ 1074 boolean confirmActionAsVariable(String actionName, String variableName) { 1075 int response = JmriJOptionPane.showConfirmDialog(_editLogixFrame, 1076 Bundle.getMessage("ConfirmActionAsVariable", actionName, variableName), 1077 Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION, // NOI18N 1078 JmriJOptionPane.QUESTION_MESSAGE); 1079 return ( response == JmriJOptionPane.YES_OPTION ); 1080 } 1081 1082 /** 1083 * Check Turnout reference of text. 1084 * <p> 1085 * Show a message if not found. 1086 * 1087 * @param name the name to look for 1088 * @return the system or user name of the corresponding Turnout, null if not 1089 * found 1090 */ 1091 @CheckForNull 1092 String validateTurnoutReference(@CheckForNull String name) { 1093 Turnout t = null; 1094 if (name != null) { 1095 if (name.length() > 0) { 1096 t = InstanceManager.turnoutManagerInstance().getByUserName(name); 1097 if (t != null) { 1098 return name; 1099 } 1100 } 1101 t = InstanceManager.turnoutManagerInstance().getBySystemName(name); 1102 } 1103 if (t == null) { 1104 messageInvalidActionItemName(name, "Turnout"); // NOI18N 1105 return null; 1106 } 1107 return name; 1108 } 1109 1110 /** 1111 * Check SignalHead reference of text. 1112 * <p> 1113 * Show a message if not found. 1114 * 1115 * @param name the name to look for 1116 * @return the system or user name of the corresponding SignalHead, null if 1117 * not found 1118 */ 1119 @CheckForNull 1120 String validateSignalHeadReference(@CheckForNull String name) { 1121 SignalHead h = null; 1122 if (name != null) { 1123 if (name.length() > 0) { 1124 h = InstanceManager.getDefault(jmri.SignalHeadManager.class).getByUserName(name); 1125 if (h != null) { 1126 return name; 1127 } 1128 } 1129 h = InstanceManager.getDefault(jmri.SignalHeadManager.class).getBySystemName(name); 1130 } 1131 if (h == null) { 1132 messageInvalidActionItemName(name, "SignalHead"); // NOI18N 1133 return null; 1134 } 1135 return name; 1136 } 1137 1138 /** 1139 * Check SignalMast reference of text. 1140 * <p> 1141 * Show a message if not found. 1142 * 1143 * @param name the name to look for 1144 * @return the system or user name of the corresponding Signal Mast, null if 1145 * not found 1146 */ 1147 @CheckForNull 1148 String validateSignalMastReference(@CheckForNull String name) { 1149 SignalMast h = null; 1150 if (name != null) { 1151 if (name.length() > 0) { 1152 h = InstanceManager.getDefault(jmri.SignalMastManager.class).getByUserName(name); 1153 if (h != null) { 1154 return name; 1155 } 1156 } 1157 try { 1158 h = InstanceManager.getDefault(jmri.SignalMastManager.class).provideSignalMast(name); 1159 } catch (IllegalArgumentException ex) { 1160 h = null; // tested below 1161 } 1162 } 1163 if (h == null) { 1164 messageInvalidActionItemName(name, "SignalMast"); // NOI18N 1165 return null; 1166 } 1167 return name; 1168 } 1169 1170 /** 1171 * Check Warrant reference of text. 1172 * <p> 1173 * Show a message if not found. 1174 * 1175 * @param name the name to look for 1176 * @return the system or user name of the corresponding Warrant, null if not 1177 * found 1178 */ 1179 @CheckForNull 1180 String validateWarrantReference(@CheckForNull String name) { 1181 Warrant w = null; 1182 if (name != null) { 1183 if (name.length() > 0) { 1184 w = InstanceManager.getDefault(WarrantManager.class).getByUserName(name); 1185 if (w != null) { 1186 return name; 1187 } 1188 } 1189 w = InstanceManager.getDefault(WarrantManager.class).getBySystemName(name); 1190 } 1191 if (w == null) { 1192 messageInvalidActionItemName(name, "Warrant"); // NOI18N 1193 return null; 1194 } 1195 return name; 1196 } 1197 1198 /** 1199 * Check OBlock reference of text. 1200 * <p> 1201 * Show a message if not found. 1202 * 1203 * @param name the name to look for 1204 * @return the system or user name of the corresponding OBlock, null if not 1205 * found 1206 */ 1207 @CheckForNull 1208 String validateOBlockReference(@CheckForNull String name) { 1209 OBlock b = null; 1210 if (name != null) { 1211 if (name.length() > 0) { 1212 b = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getByUserName(name); 1213 if (b != null) { 1214 return name; 1215 } 1216 } 1217 b = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getBySystemName(name); 1218 } 1219 if (b == null) { 1220 messageInvalidActionItemName(name, "OBlock"); // NOI18N 1221 return null; 1222 } 1223 return name; 1224 } 1225 1226 /** 1227 * Check Sensor reference of text. 1228 * <p> 1229 * Show a message if not found. 1230 * 1231 * @param name the name to look for 1232 * @return the system or user name of the corresponding Sensor, null if not 1233 * found 1234 */ 1235 @CheckForNull 1236 String validateSensorReference(@CheckForNull String name) { 1237 Sensor s = null; 1238 if (name != null) { 1239 if (name.length() > 0) { 1240 s = InstanceManager.getDefault(jmri.SensorManager.class).getByUserName(name); 1241 if (s != null) { 1242 return name; 1243 } 1244 } 1245 s = InstanceManager.getDefault(jmri.SensorManager.class).getBySystemName(name); 1246 } 1247 if (s == null) { 1248 messageInvalidActionItemName(name, "Sensor"); // NOI18N 1249 return null; 1250 } 1251 return name; 1252 } 1253 1254 /** 1255 * Check Light reference of text. 1256 * <p> 1257 * Show a message if not found. 1258 * 1259 * @param name the name to look for 1260 * @return the system or user name of the corresponding Light, null if not 1261 * found 1262 */ 1263 @CheckForNull 1264 String validateLightReference(@CheckForNull String name) { 1265 Light l = null; 1266 if (name != null) { 1267 if (name.length() > 0) { 1268 l = InstanceManager.lightManagerInstance().getByUserName(name); 1269 if (l != null) { 1270 return name; 1271 } 1272 } 1273 l = InstanceManager.lightManagerInstance().getBySystemName(name); 1274 } 1275 if (l == null) { 1276 messageInvalidActionItemName(name, "Light"); // NOI18N 1277 return null; 1278 } 1279 return name; 1280 } 1281 1282 /** 1283 * Check Conditional reference of text. 1284 * <p> 1285 * Show a message if not found. 1286 * 1287 * @param name the name to look for 1288 * @return the system or user name of the corresponding Conditional, null if 1289 * not found 1290 */ 1291 @CheckForNull 1292 String validateConditionalReference(@CheckForNull String name) { 1293 Conditional c = null; 1294 if (name != null) { 1295 if (name.length() > 0) { 1296 c = _conditionalManager.getByUserName(name); 1297 if (c != null) { 1298 return name; 1299 } 1300 } 1301 c = _conditionalManager.getBySystemName(name); 1302 } 1303 if (c == null) { 1304 messageInvalidActionItemName(name, "Conditional"); // NOI18N 1305 return null; 1306 } 1307 return name; 1308 } 1309 1310 /** 1311 * Check Logix reference of text. 1312 * <p> 1313 * Show a message if not found. 1314 * 1315 * @param name the name to look for 1316 * @return the system or user name of the corresponding Logix, null if not 1317 * found 1318 */ 1319 @CheckForNull 1320 String validateLogixReference(@CheckForNull String name) { 1321 Logix l = null; 1322 if (name != null) { 1323 if (name.length() > 0) { 1324 l = _logixManager.getByUserName(name); 1325 if (l != null) { 1326 return name; 1327 } 1328 } 1329 l = _logixManager.getBySystemName(name); 1330 } 1331 if (l == null) { 1332 messageInvalidActionItemName(name, "Logix"); // NOI18N 1333 return null; 1334 } 1335 return name; 1336 } 1337 1338 /** 1339 * Check Route reference of text. 1340 * <p> 1341 * Show a message if not found. 1342 * 1343 * @param name the name to look for 1344 * @return the system or user name of the corresponding Route, null if not 1345 * found 1346 */ 1347 @CheckForNull 1348 String validateRouteReference(@CheckForNull String name) { 1349 Route r = null; 1350 if (name != null) { 1351 if (name.length() > 0) { 1352 r = InstanceManager.getDefault(jmri.RouteManager.class).getByUserName(name); 1353 if (r != null) { 1354 return name; 1355 } 1356 } 1357 r = InstanceManager.getDefault(jmri.RouteManager.class).getBySystemName(name); 1358 } 1359 if (r == null) { 1360 messageInvalidActionItemName(name, "Route"); // NOI18N 1361 return null; 1362 } 1363 return name; 1364 } 1365 1366 /** 1367 * Check an Audio reference of text. 1368 * <p> 1369 * Show a message if not found. 1370 * 1371 * @param name the name to look for 1372 * @return the system or user name of the corresponding AudioManager, null 1373 * if not found 1374 */ 1375 @CheckForNull 1376 String validateAudioReference(@CheckForNull String name) { 1377 Audio a = null; 1378 if (name != null) { 1379 if (name.length() > 0) { 1380 a = InstanceManager.getDefault(jmri.AudioManager.class).getByUserName(name); 1381 if (a != null) { 1382 return name; 1383 } 1384 } 1385 a = InstanceManager.getDefault(jmri.AudioManager.class).getBySystemName(name); 1386 } 1387 if (a == null || (a.getSubType() != Audio.SOURCE && a.getSubType() != Audio.LISTENER)) { 1388 messageInvalidActionItemName(name, "Audio"); // NOI18N 1389 return null; 1390 } 1391 return name; 1392 } 1393 1394 /** 1395 * Check an EntryExit reference of text. 1396 * <p> 1397 * Show a message if not found. 1398 * 1399 * @param name the name to look for 1400 * @return the system name of the corresponding EntryExit pair, null if not 1401 * found 1402 */ 1403 @CheckForNull 1404 String validateEntryExitReference(@CheckForNull String name) { 1405 NamedBean nb; 1406 if (name != null && name.length() > 0) { 1407 nb = jmri.InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class).getNamedBean(name); 1408 if (nb != null) { 1409 return nb.getSystemName(); 1410 } 1411 } 1412 messageInvalidActionItemName(name, "BeanNameEntryExit"); // NOI18N 1413 return null; 1414 } 1415 1416 /** 1417 * Get Light instance. 1418 * <p> 1419 * Show a message if not found. 1420 * 1421 * @param name user or system name of an existing light 1422 * @return the Light object 1423 */ 1424 @CheckForNull 1425 Light getLight( @CheckForNull String name) { 1426 if (name == null) { 1427 return null; 1428 } 1429 Light l = null; 1430 if (name.length() > 0) { 1431 l = InstanceManager.lightManagerInstance().getByUserName(name); 1432 if (l != null) { 1433 return l; 1434 } 1435 l = InstanceManager.lightManagerInstance().getBySystemName(name); 1436 } 1437 if (l == null) { 1438 messageInvalidActionItemName(name, "Light"); // NOI18N 1439 } 1440 return l; 1441 } 1442 1443 int parseTime(@Nonnull String s) { 1444 int nHour = 0; 1445 int nMin = 0; 1446 boolean error = false; 1447 int index = s.indexOf(':'); 1448 String hour = null; 1449 String minute = null; 1450 try { 1451 if (index > 0) { // : after start 1452 hour = s.substring(0, index); 1453 if (index + 1 < s.length()) { // check for : at end 1454 minute = s.substring(index + 1); 1455 } else { 1456 minute = "0"; 1457 } 1458 } else if (index == 0) { // : at start 1459 hour = "0"; 1460 minute = s.substring(index + 1); 1461 } else { 1462 hour = s; 1463 minute = "0"; 1464 } 1465 } catch (IndexOutOfBoundsException ioob) { 1466 error = true; 1467 } 1468 if (!error) { 1469 try { 1470 nHour = Integer.parseInt(hour); 1471 if ((nHour < 0) || (nHour > 24)) { 1472 error = true; 1473 } 1474 nMin = Integer.parseInt(minute); 1475 if ((nMin < 0) || (nMin > 59)) { 1476 error = true; 1477 } 1478 } catch (NumberFormatException e) { 1479 error = true; 1480 } 1481 } 1482 if (error) { 1483 // if unsuccessful, print error message 1484 JmriJOptionPane.showMessageDialog(_editLogixFrame, 1485 Bundle.getMessage("Error26", s), 1486 Bundle.getMessage("ErrorTitle"), // NOI18N 1487 JmriJOptionPane.ERROR_MESSAGE); 1488 return (-1); 1489 } 1490 // here if successful 1491 return ((nHour * 60) + nMin); 1492 } 1493 1494 /** 1495 * Format time to hh:mm given integer hour and minute. 1496 * 1497 * @param hour value for time hours 1498 * @param minute value for time minutes 1499 * @return Formatted time string 1500 */ 1501 public static String formatTime(int hour, int minute) { 1502 String s = ""; 1503 String t = Integer.toString(hour); 1504 if (t.length() == 2) { 1505 s = t + ":"; 1506 } else if (t.length() == 1) { 1507 s = "0" + t + ":"; 1508 } 1509 t = Integer.toString(minute); 1510 if (t.length() == 2) { 1511 s += t; 1512 } else if (t.length() == 1) { 1513 s = s + "0" + t; 1514 } 1515 if (s.length() != 5) { 1516 // input error 1517 s = "00:00"; 1518 } 1519 return s; 1520 } 1521 1522 // ------------ Error Dialogs ------------ 1523 1524 /** 1525 * Send an Invalid Conditional Action name message for Edit Logix pane. 1526 * 1527 * @param name user or system name to look up 1528 * @param itemType type of Bean to look for 1529 */ 1530 void messageInvalidActionItemName(String name, String itemType) { 1531 JmriJOptionPane.showMessageDialog(_editLogixFrame, 1532 Bundle.getMessage("Error22", name, Bundle.getMessage("BeanName" + itemType)), 1533 Bundle.getMessage("ErrorTitle"), // NOI18N 1534 JmriJOptionPane.ERROR_MESSAGE); 1535 } 1536 1537 /** 1538 * Send a duplicate Conditional user name message for Edit Logix pane. 1539 * 1540 * @param svName proposed name that duplicates an existing name 1541 */ 1542 void messageDuplicateConditionalUserName(String svName) { 1543 JmriJOptionPane.showMessageDialog(_editLogixFrame, 1544 Bundle.getMessage("Error30", svName), 1545 Bundle.getMessage("ErrorTitle"), // NOI18N 1546 JmriJOptionPane.ERROR_MESSAGE); 1547 } 1548 1549 public void bringToFront() { 1550 _editLogixFrame.toFront(); 1551 } 1552 1553 public void locateAt(Component c) { 1554 _editLogixFrame.setLocationRelativeTo(c); 1555 _editLogixFrame.toFront(); 1556 } 1557 1558 protected String getClassName() { 1559 return ConditionalEditBase.class.getName(); 1560 } 1561 1562 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConditionalEditBase.class); 1563 1564}