001package jmri.jmrit.logixng.actions; 002 003import java.awt.FlowLayout; 004import java.awt.event.ActionEvent; 005import java.beans.*; 006import java.util.*; 007 008import javax.swing.*; 009 010import jmri.*; 011import jmri.jmrit.logixng.*; 012import jmri.jmrit.logixng.implementation.DefaultSymbolTable; 013import jmri.jmrit.logixng.util.ReferenceUtil; 014import jmri.jmrit.logixng.util.parser.*; 015import jmri.jmrit.logixng.util.parser.ExpressionNode; 016import jmri.jmrit.logixng.util.parser.RecursiveDescentParser; 017import jmri.util.ThreadingUtil; 018import jmri.util.TypeConversionUtil; 019 020/** 021 * This action show a dialog. 022 * 023 * @author Daniel Bergqvist Copyright 2021 024 */ 025public class ShowDialog extends AbstractDigitalAction 026 implements FemaleSocketListener, PropertyChangeListener, VetoableChangeListener { 027 028 private static final ResourceBundle rbx = 029 ResourceBundle.getBundle("jmri.jmrit.logixng.implementation.ImplementationBundle"); 030 031 private String _validateSocketSystemName; 032 private final FemaleDigitalExpressionSocket _validateSocket; 033 private String _executeSocketSystemName; 034 private final FemaleDigitalActionSocket _executeSocket; 035 private Set<Button> _enabledButtons = new HashSet<>(); 036 private String _localVariableForSelectedButton = ""; 037 private String _localVariableForInputString = ""; 038 private boolean _modal = true; 039 private boolean _multiLine = false; 040 private FormatType _formatType = FormatType.OnlyText; 041 private String _format = ""; 042 private final List<Data> _dataList = new ArrayList<>(); 043 private final InternalFemaleSocket _internalSocket = new InternalFemaleSocket(); 044 private JDialog _dialog; 045 046 047 public ShowDialog(String sys, String user) 048 throws BadUserNameException, BadSystemNameException { 049 super(sys, user); 050 _validateSocket = InstanceManager.getDefault(DigitalExpressionManager.class) 051 .createFemaleSocket(this, this, Bundle.getMessage("ShowDialog_SocketValidate")); 052 _executeSocket = InstanceManager.getDefault(DigitalActionManager.class) 053 .createFemaleSocket(this, this, Bundle.getMessage("ShowDialog_SocketExecute")); 054 } 055 056 @Override 057 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) 058 throws ParserException, JmriException { 059 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 060 String sysName = systemNames.get(getSystemName()); 061 String userName = userNames.get(getSystemName()); 062 if (sysName == null) sysName = manager.getAutoSystemName(); 063 ShowDialog copy = new ShowDialog(sysName, userName); 064 copy.setComment(getComment()); 065 for (Button button : _enabledButtons) { 066 copy.getEnabledButtons().add(button); 067 } 068 copy.setLocalVariableForSelectedButton(_localVariableForSelectedButton); 069 copy.setLocalVariableForInputString(_localVariableForInputString); 070 copy.setModal(_modal); 071 copy.setMultiLine(_multiLine); 072 copy.setFormat(_format); 073 copy.setFormatType(_formatType); 074 for (Data data : _dataList) { 075 copy.getDataList().add(new Data(data)); 076 } 077 return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames); 078 } 079 080 /** 081 * Return the list of buttons. 082 * @return the list of buttons. 083 */ 084 public Set<Button> getEnabledButtons() { 085 return this._enabledButtons; 086 } 087 088 public void setModal(boolean modal) { 089 _modal = modal; 090 } 091 092 public boolean getModal() { 093 return _modal; 094 } 095 096 public void setMultiLine(boolean multiLine) { 097 _multiLine = multiLine; 098 } 099 100 public boolean getMultiLine() { 101 return _multiLine; 102 } 103 104 public void setLocalVariableForSelectedButton(String localVariable) { 105 _localVariableForSelectedButton = localVariable; 106 } 107 108 public String getLocalVariableForSelectedButton() { 109 return _localVariableForSelectedButton; 110 } 111 112 public void setLocalVariableForInputString(String localVariableForInputString) { 113 _localVariableForInputString = localVariableForInputString; 114 } 115 116 public String getLocalVariableForInputString() { 117 return _localVariableForInputString; 118 } 119 120 public void setFormatType(FormatType formatType) { 121 _formatType = formatType; 122 } 123 124 public FormatType getFormatType() { 125 return _formatType; 126 } 127 128 public void setFormat(String format) { 129 _format = format; 130 } 131 132 public String getFormat() { 133 return _format; 134 } 135 136 public List<Data> getDataList() { 137 return _dataList; 138 } 139 140 @Override 141 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 142/* 143 if ("CanDelete".equals(evt.getPropertyName())) { // No I18N 144 if (evt.getOldValue() instanceof Memory) { 145 if (evt.getOldValue().equals(getMemory().getBean())) { 146 throw new PropertyVetoException(getDisplayName(), evt); 147 } 148 } 149 } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N 150 if (evt.getOldValue() instanceof Memory) { 151 if (evt.getOldValue().equals(getMemory().getBean())) { 152 setMemory((Memory)null); 153 } 154 } 155 } 156*/ 157 } 158 159 /** {@inheritDoc} */ 160 @Override 161 public Category getCategory() { 162 return Category.OTHER; 163 } 164 165 private List<Object> getDataValues() throws JmriException { 166 List<Object> values = new ArrayList<>(); 167 for (Data _data : _dataList) { 168 switch (_data._dataType) { 169 case LocalVariable: 170 values.add(getConditionalNG().getSymbolTable().getValue(_data._data)); 171 break; 172 173 case Memory: 174 MemoryManager memoryManager = InstanceManager.getDefault(MemoryManager.class); 175 Memory memory = memoryManager.getMemory(_data._data); 176 if (memory == null) throw new IllegalArgumentException("Memory '" + _data._data + "' not found"); 177 values.add(memory.getValue()); 178 break; 179 180 case Reference: 181 values.add(ReferenceUtil.getReference( 182 getConditionalNG().getSymbolTable(), _data._data)); 183 break; 184 185 case Formula: 186 if (_data._expressionNode != null) { 187 values.add(_data._expressionNode.calculate(getConditionalNG().getSymbolTable())); 188 } 189 190 break; 191 192 default: 193 throw new IllegalArgumentException("_formatType has invalid value: "+_formatType.name()); 194 } 195 } 196 return values; 197 } 198 199 /** {@inheritDoc} */ 200 @Override 201 public void execute() throws JmriException { 202 203 String str; 204 String strMultiLine; 205 206 switch (_formatType) { 207 case OnlyText: 208 str = _format; 209 break; 210 211 case CommaSeparatedList: 212 StringBuilder sb = new StringBuilder(); 213 for (Object value : getDataValues()) { 214 if (sb.length() > 0) sb.append(", "); 215 sb.append(value != null ? value.toString() : "null"); 216 } 217 str = sb.toString(); 218 break; 219 220 case StringFormat: 221 str = String.format(_format, getDataValues().toArray()); 222 break; 223 224 default: 225 throw new IllegalArgumentException("_formatType has invalid value: "+_formatType.name()); 226 } 227 228 final ConditionalNG conditionalNG = getConditionalNG(); 229 final DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(conditionalNG.getSymbolTable()); 230 231 if (_multiLine) strMultiLine = "<html>" + str + "</html>"; 232 else strMultiLine = str; 233 234 Object value = null; 235 if (!_localVariableForInputString.isEmpty()) { 236 value = newSymbolTable.getValue(_localVariableForInputString); 237 } 238 final Object currentValue = value; 239 240 ThreadingUtil.runOnGUIEventually(() -> { 241 242 if (_dialog != null) _dialog.dispose(); 243 244 _dialog = new JDialog( 245 (JFrame)null, 246 Bundle.getMessage("ShowDialog_Title"), 247 _modal); 248 249 _dialog.addWindowListener(new java.awt.event.WindowAdapter() { 250 @Override 251 public void windowClosing(java.awt.event.WindowEvent e) { 252 _dialog = null; 253 } 254 }); 255 256 JPanel panel = new JPanel(); 257 panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 258 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 259 _dialog.getContentPane().add(panel); 260 261 panel.add(new JLabel(strMultiLine)); 262 263 JTextField textField = new JTextField(20); 264 if (!_localVariableForInputString.isEmpty()) { 265 if (currentValue != null) { 266 String strValue = TypeConversionUtil.convertToString(currentValue, false); 267 textField.setText(strValue); 268 } 269 panel.add(textField); 270 } 271 272 JPanel buttonPanel = new JPanel(); 273 buttonPanel.setLayout(new FlowLayout()); 274 275 for (Button button : Button.values()) { 276 if (_enabledButtons.contains(button)) { 277 JButton jbutton = new JButton(button._text); 278 jbutton.addActionListener((ActionEvent e) -> { 279 synchronized(ShowDialog.this) { 280 _internalSocket.conditionalNG = conditionalNG; 281 _internalSocket.newSymbolTable = newSymbolTable; 282 _internalSocket.selectedButton = button._value; 283 _internalSocket.inputValue = textField.getText(); 284 conditionalNG.execute(_internalSocket); 285 } 286 }); 287 buttonPanel.add(jbutton); 288 } 289 } 290 panel.add(buttonPanel); 291 292 _dialog.pack(); 293 _dialog.setLocationRelativeTo(null); 294 _dialog.setVisible(true); 295 }); 296 } 297 298 @Override 299 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 300 switch (index) { 301 case 0: 302 return _validateSocket; 303 304 case 1: 305 return _executeSocket; 306 307 default: 308 throw new IllegalArgumentException( 309 String.format("index has invalid value: %d", index)); 310 } 311 } 312 313 @Override 314 public int getChildCount() { 315 return 2; 316 } 317 318 @Override 319 public void connected(FemaleSocket socket) { 320 if (socket == _validateSocket) { 321 _validateSocketSystemName = socket.getConnectedSocket().getSystemName(); 322 } else if (socket == _executeSocket) { 323 _executeSocketSystemName = socket.getConnectedSocket().getSystemName(); 324 } else { 325 throw new IllegalArgumentException("unkown socket"); 326 } 327 } 328 329 @Override 330 public void disconnected(FemaleSocket socket) { 331 if (socket == _validateSocket) { 332 _validateSocketSystemName = null; 333 } else if (socket == _executeSocket) { 334 _executeSocketSystemName = null; 335 } else { 336 throw new IllegalArgumentException("unkown socket"); 337 } 338 } 339 340 @Override 341 public String getShortDescription(Locale locale) { 342 return Bundle.getMessage(locale, "ShowDialog_Short"); 343 } 344 345 @Override 346 public String getLongDescription(Locale locale) { 347 String bundleKey; 348 switch (_formatType) { 349 case OnlyText: 350 bundleKey = "ShowDialog_Long_TextOnly"; 351 break; 352 case CommaSeparatedList: 353 bundleKey = "ShowDialog_Long_CommaSeparatedList"; 354 break; 355 case StringFormat: 356 bundleKey = "ShowDialog_Long_StringFormat"; 357 break; 358 default: 359 throw new RuntimeException("_formatType has unknown value: "+_formatType.name()); 360 } 361 return Bundle.getMessage(locale, bundleKey, _format); 362 } 363 364 public FemaleDigitalExpressionSocket getValidateSocket() { 365 return _validateSocket; 366 } 367 368 public String getValidateSocketSystemName() { 369 return _validateSocketSystemName; 370 } 371 372 public void setValidateSocketSystemName(String systemName) { 373 _validateSocketSystemName = systemName; 374 } 375 376 public FemaleDigitalActionSocket getExecuteSocket() { 377 return _executeSocket; 378 } 379 380 public String getExecuteSocketSystemName() { 381 return _executeSocketSystemName; 382 } 383 384 public void setExecuteSocketSystemName(String systemName) { 385 _executeSocketSystemName = systemName; 386 } 387 388 /** {@inheritDoc} */ 389 @Override 390 public void setup() { 391 try { 392 if (!_validateSocket.isConnected() 393 || !_validateSocket.getConnectedSocket().getSystemName() 394 .equals(_validateSocketSystemName)) { 395 396 String socketSystemName = _validateSocketSystemName; 397 398 _validateSocket.disconnect(); 399 400 if (socketSystemName != null) { 401 MaleSocket maleSocket = 402 InstanceManager.getDefault(DigitalExpressionManager.class) 403 .getBySystemName(socketSystemName); 404 if (maleSocket != null) { 405 _validateSocket.connect(maleSocket); 406 maleSocket.setup(); 407 } else { 408 log.error("cannot load digital expression {}", socketSystemName); 409 } 410 } 411 } else { 412 _validateSocket.getConnectedSocket().setup(); 413 } 414 415 if (!_executeSocket.isConnected() 416 || !_executeSocket.getConnectedSocket().getSystemName() 417 .equals(_executeSocketSystemName)) { 418 419 String socketSystemName = _executeSocketSystemName; 420 421 _executeSocket.disconnect(); 422 423 if (socketSystemName != null) { 424 MaleSocket maleSocket = 425 InstanceManager.getDefault(DigitalActionManager.class) 426 .getBySystemName(socketSystemName); 427 if (maleSocket != null) { 428 _executeSocket.connect(maleSocket); 429 maleSocket.setup(); 430 } else { 431 log.error("cannot load digital action {}", socketSystemName); 432 } 433 } 434 } else { 435 _executeSocket.getConnectedSocket().setup(); 436 } 437 } catch (SocketAlreadyConnectedException ex) { 438 // This shouldn't happen and is a runtime error if it does. 439 throw new RuntimeException("socket is already connected"); 440 } 441 } 442 443 /** {@inheritDoc} */ 444 @Override 445 public void registerListenersForThisClass() { 446 // Do nothing 447 } 448 449 /** {@inheritDoc} */ 450 @Override 451 public void unregisterListenersForThisClass() { 452 // Do nothing 453 } 454 455 /** {@inheritDoc} */ 456 @Override 457 public void propertyChange(PropertyChangeEvent evt) { 458 getConditionalNG().execute(); 459 } 460 461 /** {@inheritDoc} */ 462 @Override 463 public void disposeMe() { 464 } 465 466 467 /** {@inheritDoc} */ 468 @Override 469 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 470/* 471 log.debug("getUsageReport :: ShowDialog: bean = {}, report = {}", cdl, report); 472 for (NamedBeanReference namedBeanReference : _namedBeanReferences.values()) { 473 if (namedBeanReference._handle != null) { 474 if (bean.equals(namedBeanReference._handle.getBean())) { 475 report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription())); 476 } 477 } 478 } 479*/ 480 } 481 482 483 public enum FormatType { 484 OnlyText(Bundle.getMessage("ShowDialog_FormatType_TextOnly"), true, false), 485 CommaSeparatedList(Bundle.getMessage("ShowDialog_FormatType_CommaSeparatedList"), false, true), 486 StringFormat(Bundle.getMessage("ShowDialog_FormatType_StringFormat"), true, true); 487 488 private final String _text; 489 private final boolean _useFormat; 490 private final boolean _useData; 491 492 private FormatType(String text, boolean useFormat, boolean useData) { 493 this._text = text; 494 this._useFormat = useFormat; 495 this._useData = useData; 496 } 497 498 @Override 499 public String toString() { 500 return _text; 501 } 502 503 public boolean getUseFormat() { 504 return _useFormat; 505 } 506 507 public boolean getUseData() { 508 return _useData; 509 } 510 511 } 512 513 514 public enum Button { 515 Ok(1, Bundle.getMessage("ButtonOK")), 516 Cancel(2, Bundle.getMessage("ButtonCancel")), 517 Yes(3, Bundle.getMessage("ButtonYes")), 518 No(4, Bundle.getMessage("ButtonNo")); 519 520 private final int _value; 521 private final String _text; 522 523 private Button(int value, String text) { 524 this._value = value; 525 this._text = text; 526 } 527 528 public int getValue() { 529 return _value; 530 } 531 532 @Override 533 public String toString() { 534 return _text; 535 } 536 537 } 538 539 540 public enum DataType { 541 LocalVariable(Bundle.getMessage("ShowDialog_Operation_LocalVariable")), 542 Memory(Bundle.getMessage("ShowDialog_Operation_Memory")), 543 Reference(Bundle.getMessage("ShowDialog_Operation_Reference")), 544 Formula(Bundle.getMessage("ShowDialog_Operation_Formula")); 545 546 private final String _text; 547 548 private DataType(String text) { 549 this._text = text; 550 } 551 552 @Override 553 public String toString() { 554 return _text; 555 } 556 557 } 558 559 560 public static class Data { 561 562 private DataType _dataType = DataType.LocalVariable; 563 private String _data = ""; 564 private ExpressionNode _expressionNode; 565 566 public Data(Data data) throws ParserException { 567 _dataType = data._dataType; 568 _data = data._data; 569 calculateFormula(); 570 } 571 572 public Data(DataType dataType, String data) throws ParserException { 573 if (dataType != null) { 574 _dataType = dataType; 575 } else { 576 // Sometimes data entered in a JTable is not updated correctly 577 log.warn("dataType is null"); 578 } 579 _data = data; 580 calculateFormula(); 581 } 582 583 private void calculateFormula() throws ParserException { 584 if (_dataType == DataType.Formula) { 585 Map<String, Variable> variables = new HashMap<>(); 586 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 587 _expressionNode = parser.parseExpression(_data); 588 } else { 589 _expressionNode = null; 590 } 591 } 592 593 public void setDataType(DataType dataType) { 594 if (dataType != null) { 595 _dataType = dataType; 596 } else { 597 // Sometimes data entered in a JTable is not updated correctly 598 log.warn("dataType is null"); 599 } 600 } 601 602 public DataType getDataType() { return _dataType; } 603 604 public void setData(String data) { _data = data; } 605 public String getData() { return _data; } 606 607 } 608 609 610 private class InternalFemaleSocket extends jmri.jmrit.logixng.implementation.DefaultFemaleDigitalActionSocket { 611 612 private ConditionalNG conditionalNG; 613 private SymbolTable newSymbolTable; 614 private int selectedButton; 615 private String inputValue; 616 617 public InternalFemaleSocket() { 618 super(null, new FemaleSocketListener(){ 619 @Override 620 public void connected(FemaleSocket socket) { 621 // Do nothing 622 } 623 624 @Override 625 public void disconnected(FemaleSocket socket) { 626 // Do nothing 627 } 628 }, "A"); 629 } 630 631 @Override 632 public void execute() throws JmriException { 633 if (_executeSocket != null) { 634 MaleSocket maleSocket = (MaleSocket)ShowDialog.this.getParent(); 635 try { 636 SymbolTable oldSymbolTable = conditionalNG.getSymbolTable(); 637 conditionalNG.setSymbolTable(newSymbolTable); 638 if (!_localVariableForSelectedButton.isEmpty()) { 639 newSymbolTable.setValue(_localVariableForSelectedButton, selectedButton); 640 } 641 if (!_localVariableForInputString.isEmpty()) { 642 newSymbolTable.setValue(_localVariableForInputString, inputValue); 643 } 644 boolean result = true; 645 if (_validateSocket.isConnected()) { 646 result = _validateSocket.evaluate(); 647 } 648 if (result) { 649 _dialog.dispose(); 650 _executeSocket.execute(); 651 } 652 conditionalNG.setSymbolTable(oldSymbolTable); 653 } catch (JmriException e) { 654 if (e.getErrors() != null) { 655 maleSocket.handleError(ShowDialog.this, rbx.getString("ExceptionExecuteMulti"), e.getErrors(), e, log); 656 } else { 657 maleSocket.handleError(ShowDialog.this, Bundle.formatMessage(rbx.getString("ExceptionExecuteAction"), e.getLocalizedMessage()), e, log); 658 } 659 } catch (RuntimeException e) { 660 maleSocket.handleError(ShowDialog.this, Bundle.formatMessage(rbx.getString("ExceptionExecuteAction"), e.getLocalizedMessage()), e, log); 661 } 662 } 663 } 664 665 } 666 667 668 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ShowDialog.class); 669 670}