001package jmri.jmrit.logixng.actions;
002
003import java.awt.BorderLayout;
004import java.awt.FlowLayout;
005import java.awt.event.ActionEvent;
006import java.beans.*;
007import java.util.*;
008
009import javax.swing.*;
010
011import jmri.*;
012import jmri.jmrit.logixng.*;
013import jmri.jmrit.logixng.implementation.DefaultSymbolTable;
014import jmri.jmrit.logixng.util.ReferenceUtil;
015import jmri.jmrit.logixng.util.parser.*;
016import jmri.jmrit.logixng.util.parser.ExpressionNode;
017import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
018import jmri.util.ThreadingUtil;
019import jmri.util.TypeConversionUtil;
020
021/**
022 * This action show a dialog.
023 *
024 * @author Daniel Bergqvist Copyright 2021
025 */
026public class ShowDialog extends AbstractDigitalAction
027        implements FemaleSocketListener, PropertyChangeListener, VetoableChangeListener {
028
029    private static final ResourceBundle rbx =
030            ResourceBundle.getBundle("jmri.jmrit.logixng.implementation.ImplementationBundle");
031
032    private String _validateSocketSystemName;
033    private final FemaleDigitalExpressionSocket _validateSocket;
034    private String _executeSocketSystemName;
035    private final FemaleDigitalActionSocket _executeSocket;
036    private Set<Button> _enabledButtons = new HashSet<>();
037    private String _localVariableForSelectedButton = "";
038    private String _localVariableForInputString = "";
039    private boolean _modal = true;
040    private boolean _multiLine = false;
041    private FormatType _formatType = FormatType.OnlyText;
042    private String _format = "";
043    private final List<Data> _dataList = new ArrayList<>();
044    private final InternalFemaleSocket _internalSocket = new InternalFemaleSocket();
045    private JDialog _dialog;
046
047
048    public ShowDialog(String sys, String user)
049            throws BadUserNameException, BadSystemNameException {
050        super(sys, user);
051        _validateSocket = InstanceManager.getDefault(DigitalExpressionManager.class)
052                .createFemaleSocket(this, this, Bundle.getMessage("ShowDialog_SocketValidate"));
053        _executeSocket = InstanceManager.getDefault(DigitalActionManager.class)
054                .createFemaleSocket(this, this, Bundle.getMessage("ShowDialog_SocketExecute"));
055    }
056
057    @Override
058    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames)
059            throws ParserException, JmriException {
060        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
061        String sysName = systemNames.get(getSystemName());
062        String userName = userNames.get(getSystemName());
063        if (sysName == null) sysName = manager.getAutoSystemName();
064        ShowDialog copy = new ShowDialog(sysName, userName);
065        copy.setComment(getComment());
066        for (Button button : _enabledButtons) {
067            copy.getEnabledButtons().add(button);
068        }
069        copy.setLocalVariableForSelectedButton(_localVariableForSelectedButton);
070        copy.setLocalVariableForInputString(_localVariableForInputString);
071        copy.setModal(_modal);
072        copy.setMultiLine(_multiLine);
073        copy.setFormat(_format);
074        copy.setFormatType(_formatType);
075        for (Data data : _dataList) {
076            copy.getDataList().add(new Data(data));
077        }
078        return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames);
079    }
080
081    /**
082     * Return the list of buttons.
083     * @return the list of buttons.
084     */
085    public Set<Button> getEnabledButtons() {
086        return this._enabledButtons;
087    }
088
089    public void setModal(boolean modal) {
090        _modal = modal;
091    }
092
093    public boolean getModal() {
094        return _modal;
095    }
096
097    public void setMultiLine(boolean multiLine) {
098        _multiLine = multiLine;
099    }
100
101    public boolean getMultiLine() {
102        return _multiLine;
103    }
104
105    public void setLocalVariableForSelectedButton(String localVariable) {
106        _localVariableForSelectedButton = localVariable;
107    }
108
109    public String getLocalVariableForSelectedButton() {
110        return _localVariableForSelectedButton;
111    }
112
113    public void setLocalVariableForInputString(String localVariableForInputString) {
114        _localVariableForInputString = localVariableForInputString;
115    }
116
117    public String getLocalVariableForInputString() {
118        return _localVariableForInputString;
119    }
120
121    public void setFormatType(FormatType formatType) {
122        _formatType = formatType;
123    }
124
125    public FormatType getFormatType() {
126        return _formatType;
127    }
128
129    public void setFormat(String format) {
130        _format = format;
131    }
132
133    public String getFormat() {
134        return _format;
135    }
136
137    public List<Data> getDataList() {
138        return _dataList;
139    }
140
141    @Override
142    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
143/*
144        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
145            if (evt.getOldValue() instanceof Memory) {
146                if (evt.getOldValue().equals(getMemory().getBean())) {
147                    throw new PropertyVetoException(getDisplayName(), evt);
148                }
149            }
150        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
151            if (evt.getOldValue() instanceof Memory) {
152                if (evt.getOldValue().equals(getMemory().getBean())) {
153                    setMemory((Memory)null);
154                }
155            }
156        }
157*/
158    }
159
160    /** {@inheritDoc} */
161    @Override
162    public Category getCategory() {
163        return Category.OTHER;
164    }
165
166    private List<Object> getDataValues() throws JmriException {
167        List<Object> values = new ArrayList<>();
168        for (Data _data : _dataList) {
169            switch (_data._dataType) {
170                case LocalVariable:
171                    values.add(getConditionalNG().getSymbolTable().getValue(_data._data));
172                    break;
173
174                case Memory:
175                    MemoryManager memoryManager = InstanceManager.getDefault(MemoryManager.class);
176                    Memory memory = memoryManager.getMemory(_data._data);
177                    if (memory == null) throw new IllegalArgumentException("Memory '" + _data._data + "' not found");
178                    values.add(memory.getValue());
179                    break;
180
181                case Reference:
182                    values.add(ReferenceUtil.getReference(
183                            getConditionalNG().getSymbolTable(), _data._data));
184                    break;
185
186                case Formula:
187                    if (_data._expressionNode != null) {
188                        values.add(_data._expressionNode.calculate(getConditionalNG().getSymbolTable()));
189                    }
190
191                    break;
192
193                default:
194                    throw new IllegalArgumentException("_formatType has invalid value: "+_formatType.name());
195            }
196        }
197        return values;
198    }
199
200    /** {@inheritDoc} */
201    @Override
202    public void execute() throws JmriException {
203
204        String str;
205        String strMultiLine;
206
207        switch (_formatType) {
208            case OnlyText:
209                str = _format;
210                break;
211
212            case CommaSeparatedList:
213                StringBuilder sb = new StringBuilder();
214                for (Object value : getDataValues()) {
215                    if (sb.length() > 0) sb.append(", ");
216                    sb.append(value != null ? value.toString() : "null");
217                }
218                str = sb.toString();
219                break;
220
221            case StringFormat:
222                str = String.format(_format, getDataValues().toArray());
223                break;
224
225            default:
226                throw new IllegalArgumentException("_formatType has invalid value: "+_formatType.name());
227        }
228
229        final ConditionalNG conditionalNG = getConditionalNG();
230        final DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(conditionalNG.getSymbolTable());
231
232        if (_multiLine) strMultiLine = "<html>" + str + "</html>";
233        else strMultiLine = str;
234
235        Object value = null;
236        if (!_localVariableForInputString.isEmpty()) {
237           value = newSymbolTable.getValue(_localVariableForInputString);
238        }
239        final Object currentValue = value;
240
241        ThreadingUtil.runOnGUIEventually(() -> {
242
243            if (_dialog != null) _dialog.dispose();
244
245            _dialog = new JDialog(
246                    (JFrame)null,
247                    Bundle.getMessage("ShowDialog_Title"),
248                    _modal);
249
250            _dialog.addWindowListener(new java.awt.event.WindowAdapter() {
251                @Override
252                public void windowClosing(java.awt.event.WindowEvent e) {
253                    _dialog = null;
254                }
255            });
256
257            JPanel panel = new JPanel(new BorderLayout());
258            panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
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, BorderLayout.NORTH);
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, BorderLayout.SOUTH);
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}