001package jmri.jmrix.can.cbus.swing.nodeconfig;
002
003import java.awt.BorderLayout;
004import java.awt.Dimension;
005import java.awt.event.ActionEvent;
006import java.awt.event.ActionListener;
007import java.beans.PropertyChangeListener;
008import java.beans.PropertyChangeEvent;
009import java.util.Arrays;
010
011import javax.swing.*;
012import javax.swing.event.ChangeEvent;
013import javax.swing.event.TableModelEvent;
014import javax.swing.event.TableModelListener;
015
016import jmri.jmrix.can.CanSystemConnectionMemo;
017import jmri.jmrix.can.cbus.node.CbusNode;
018import jmri.jmrix.can.cbus.node.CbusNodeEvent;
019import jmri.jmrix.can.cbus.node.CbusNodeTableDataModel;
020import jmri.jmrix.can.cbus.node.CbusNodeSingleEventTableDataModel;
021import jmri.jmrix.can.cbus.CbusNameService;
022import jmri.util.JmriJFrame;
023import jmri.util.ThreadingUtil;
024import jmri.util.swing.JmriJOptionPane;
025import jmri.util.swing.JSpinnerUtil;
026
027/**
028 * Frame to control an instance of CBUS highlighter to highlight events.
029 *
030 * @author Steve Young Copyright (C) 2019
031 */
032public class CbusNodeEditEventFrame extends JmriJFrame
033    implements TableModelListener, PropertyChangeListener {
034
035    private CbusNodeSingleEventTableDataModel singleEVModel;
036    private final CbusNodeTableDataModel nodeModel;
037
038    private JSpinner numberSpinnerEv;
039    private JSpinner numberSpinnernd;
040
041    private JButton framedeletebutton;
042    private JButton frameeditevbutton;
043    private JButton frameCopyButton;
044    private JTabbedPane tabbedPane;
045    private CbusNodeEvent _ndEv;
046    private CbusNode _node;
047    private JLabel ndEvNameLabel;
048    private final NodeConfigToolPane mainpane;
049    private jmri.util.swing.BusyDialog busy_dialog;
050
051    private CanSystemConnectionMemo _memo;
052
053    private JPanel infoPane;
054    private JButton framenewevbutton;
055    private JButton frameResetButton;
056    private boolean isNewEvent;
057
058    /**
059     * Create a new instance of CbusNodeEditEventFrame.
060     * @param tp The main NodeConfigToolPane this is a part of
061     */
062    public CbusNodeEditEventFrame(NodeConfigToolPane tp) {
063        super();
064        mainpane = tp;
065        nodeModel = mainpane.getNodeModel();
066    }
067
068    public void initComponents(CanSystemConnectionMemo memo, CbusNodeEvent ndEv) {
069        _ndEv = ndEv;
070        _memo = memo;
071        singleEVModel = new CbusNodeSingleEventTableDataModel(memo, 5,
072        CbusNodeSingleEventTableDataModel.MAX_COLUMN, _ndEv); // controller, row, column
073        singleEVModel.addTableModelListener(this);
074
075        screenInit();
076    }
077
078    private void screenInit() {
079
080        if (infoPane != null ){ 
081            infoPane.setVisible(false);
082            infoPane = null;
083        }
084
085        if ( _ndEv == null ){
086            return;
087        }
088
089        _node = nodeModel.getNodeByNodeNum( _ndEv.getParentNn() );
090        if (_node!=null) {
091            _node.addPropertyChangeListener(this);
092        }
093        isNewEvent = _node!=null &&
094            _node.getNodeEventManager().getNodeEvent(_ndEv.getNn(), _ndEv.getEn()) == null;
095        log.debug("isNewEvent {}", isNewEvent);
096
097        frameeditevbutton = new JButton(Bundle.getMessage("EditEvent"));
098        framenewevbutton = new JButton(Bundle.getMessage("NewEvent"));
099        framedeletebutton = new JButton(Bundle.getMessage("ButtonDelete"));
100        frameResetButton = new JButton(("Reset New Values"));
101        frameCopyButton = new JButton(("Copy Event"));
102
103        numberSpinnernd = new JSpinner(new SpinnerNumberModel(Math.max(0,_ndEv.getNn()), 0, 65535, 1));
104        JSpinner.NumberEditor editor = new JSpinner.NumberEditor(numberSpinnernd, "#");
105        numberSpinnernd.setEditor(editor);
106        JSpinnerUtil.setCommitsOnValidEdit(numberSpinnernd, true);
107
108        numberSpinnerEv = new JSpinner(new SpinnerNumberModel(Math.max(0,_ndEv.getEn()), 0, 65535, 1));
109        JSpinner.NumberEditor neditor = new JSpinner.NumberEditor(numberSpinnernd, "#");
110        numberSpinnernd.setEditor(neditor);
111        JSpinnerUtil.setCommitsOnValidEdit(numberSpinnerEv, true);
112
113        infoPane = new JPanel();
114        infoPane.setLayout(new BorderLayout() );        
115
116        JPanel pageStartPanel = new JPanel();
117        pageStartPanel.setLayout(new BoxLayout(pageStartPanel, BoxLayout.Y_AXIS));
118
119        JPanel buttonPanel = new JPanel();
120
121        buttonPanel.add(frameCopyButton);
122        buttonPanel.add(frameResetButton);
123        buttonPanel.add(framedeletebutton);
124
125        JPanel spinnerPanel= new JPanel(); // row container
126
127        spinnerPanel.add(new JLabel(Bundle.getMessage("CbusNode"), JLabel.RIGHT));
128        spinnerPanel.add(numberSpinnernd);
129        spinnerPanel.add(new JLabel(Bundle.getMessage("CbusEvent"), JLabel.RIGHT));
130        spinnerPanel.add(numberSpinnerEv);
131
132        spinnerPanel.add(framenewevbutton);
133        spinnerPanel.add(frameeditevbutton);
134
135        JPanel evNamePanel= new JPanel(); // row container
136        ndEvNameLabel = new JLabel("");
137        evNamePanel.add(ndEvNameLabel);
138
139        pageStartPanel.add(spinnerPanel);
140        pageStartPanel.add(evNamePanel);
141        pageStartPanel.add( buttonPanel);
142        pageStartPanel.add(new JSeparator(SwingConstants.HORIZONTAL));
143
144        pageStartPanel.validate();
145
146        JPanel generic = new JPanel();
147
148        generic.setLayout( new BorderLayout() );
149
150        CbusNodeSingleEventEditTablePane genericEVTable = new CbusNodeSingleEventEditTablePane(singleEVModel);
151        genericEVTable.initComponents(_memo, mainpane);
152        generic.add( genericEVTable );
153
154        tabbedPane = new JTabbedPane();
155
156        tabbedPane.addTab(("Template"), null);
157        tabbedPane.addTab(("Generic"), generic);
158
159        tabbedPane.setEnabledAt(0,false);
160        tabbedPane.setSelectedIndex(1);
161
162        infoPane.add(pageStartPanel, BorderLayout.PAGE_START);
163        infoPane.add(tabbedPane, BorderLayout.CENTER);
164
165        this.add(infoPane);
166
167        this.setPreferredSize(new Dimension(500, 300));
168
169
170        this.setResizable(true);
171
172        ThreadingUtil.runOnGUI( () -> {
173            pack();
174            setVisible(true);
175        });
176
177        this.toFront();
178
179        updateButtons();
180
181        frameResetButton.addActionListener((ActionEvent e) -> {
182            singleEVModel.resetnewEVs();
183            numberSpinnernd.setValue(Math.max(0,_ndEv.getNn() ) );
184            numberSpinnerEv.setValue(Math.max(1,_ndEv.getEn() ) );
185        });
186
187
188        ActionListener copyEvClicked = ae -> {
189            log.debug("copy button");
190            isNewEvent=true;
191            updateButtons();
192        };        
193        frameCopyButton.addActionListener(copyEvClicked);
194
195        // node will check for other nodes in learn mode
196        ActionListener newEvClicked = ae -> {
197            busy_dialog = new jmri.util.swing.BusyDialog(this, "Teaching Node", false);
198            busy_dialog.start();
199         //   setEnabled(false);
200            jmri.util.ThreadingUtil.runOnLayout( ()->{
201                singleEVModel.passNewEvToNode(this);
202            });
203        };
204        framenewevbutton.addActionListener(newEvClicked);
205
206        ActionListener deleteClicked = ae -> {
207            if (_node == null ){
208                return;
209            }
210            int response = JmriJOptionPane.showConfirmDialog(
211                this,
212                Bundle.getMessage("NdDelEvConfrm",
213                    new CbusNameService(_memo).getEventNodeString(_ndEv.getNn(), _ndEv.getEn() ), _node ),
214                Bundle.getMessage("DelEvPopTitle"), 
215                JmriJOptionPane.YES_NO_OPTION,         
216                JmriJOptionPane.ERROR_MESSAGE);
217            if (response == JmriJOptionPane.YES_OPTION) {
218
219                busy_dialog = new jmri.util.swing.BusyDialog(this, "Deleting Event", false);
220                busy_dialog.start();
221              //  setEnabled(false);
222                jmri.util.ThreadingUtil.runOnLayout( ()->{
223                    _node.getNodeEventManager().deleteEvOnNode(_ndEv.getNn(), _ndEv.getEn() );
224                });
225            }
226        };
227        framedeletebutton.addActionListener(deleteClicked);
228
229        ActionListener editEvClicked = ae -> {
230            busy_dialog = new jmri.util.swing.BusyDialog(this, "Teaching Node", false);
231            busy_dialog.start();
232         //   setEnabled(false);
233            jmri.util.ThreadingUtil.runOnLayout( ()->{
234                singleEVModel.passEditEvToNode(this);
235            });
236        };
237        frameeditevbutton.addActionListener(editEvClicked);        
238
239        numberSpinnerEv.addChangeListener((ChangeEvent e) -> {
240            updateButtons();
241        });
242
243        numberSpinnernd.addChangeListener((ChangeEvent e) -> {
244            updateButtons();
245        });        
246
247    }
248
249    public boolean spinnersDirty(){
250        return ( _ndEv.getNn() != getNodeVal() ) || ( _ndEv.getEn() != getEventVal() );
251    }
252
253    private void notifyLearnEvoutcome( String message) {
254
255        _ndEv.setNn( getNodeVal() );
256        _ndEv.setEn( getEventVal() );
257        _ndEv.setEvArr( Arrays.copyOf(
258            singleEVModel.newEVs,
259            singleEVModel.newEVs.length) );
260        singleEVModel.fireTableDataChanged();
261
262        updateButtons();
263        busy_dialog.finish();
264        busy_dialog = null;
265
266        if (!message.isEmpty() ) {
267            JmriJOptionPane.showMessageDialog( this, 
268            Bundle.getMessage("NdEvVarWriteError"), Bundle.getMessage("WarningTitle"),
269            JmriJOptionPane.ERROR_MESSAGE);
270        }
271    }
272
273    private void notifyDeleteEvoutcome(String message) {
274        busy_dialog.finish();
275        busy_dialog = null;
276        updateButtons();
277        if (!message.isEmpty()) {
278            JmriJOptionPane.showMessageDialog( this, 
279            message, Bundle.getMessage("WarningTitle"),
280            JmriJOptionPane.ERROR_MESSAGE);
281        }
282        this.dispose();
283    }
284
285    /** {@inheritDoc} */
286    @Override
287    public void propertyChange(PropertyChangeEvent ev){
288        
289        if (ev.getPropertyName().equals("DELETEEVCOMPLETE")) {
290            jmri.util.ThreadingUtil.runOnGUIEventually( ()->{
291                notifyDeleteEvoutcome(ev.getNewValue().toString());
292            });
293        }
294        if (ev.getPropertyName().equals("ADDEVCOMPLETE")) {
295            jmri.util.ThreadingUtil.runOnGUIEventually( ()->{
296                notifyLearnEvoutcome(ev.getNewValue().toString());
297            });
298        }
299    }
300
301    // called on startup + when number spinners changed
302    private void updateButtons(){
303
304        setTitle( getTitle() );
305
306        ndEvNameLabel.setText("<html><div style='text-align: center;'>" + 
307        new CbusNameService(_memo).getEventNodeString(getNodeVal(), getEventVal() )
308        + "</div></html>");
309
310        frameResetButton.setEnabled(singleEVModel.isTableDirty() || spinnersDirty());
311
312        if (_node == null){
313            return;
314        }
315        framenewevbutton.setVisible(isNewEvent);
316        frameeditevbutton.setVisible(!isNewEvent);
317        framedeletebutton.setVisible(!isNewEvent);
318        frameCopyButton.setVisible(!isNewEvent);
319
320        if (isNewEvent) {
321
322            if ( _node.getNodeEventManager().getNodeEvent(getNodeVal(),getEventVal() ) == null ) {
323
324                framenewevbutton.setEnabled(true);
325                framenewevbutton.setToolTipText(null);
326
327            } else {
328                framenewevbutton.setEnabled(false);
329                framenewevbutton.setToolTipText("Event Already on Node");
330            }
331
332        }
333        else { // not new event
334            if ( spinnersDirty() || singleEVModel.isTableDirty() ){
335                if ( _node.getNodeEventManager().getNodeEvent(getNodeVal(),getEventVal() ) == null ) {
336                    frameeditevbutton.setEnabled(true);
337                    frameeditevbutton.setToolTipText(null);
338                } else {
339                    if ( spinnersDirty() ) {
340                        frameeditevbutton.setEnabled(false);
341                        frameeditevbutton.setToolTipText("Event Already on Node");
342                    }
343                    else {
344                        frameeditevbutton.setEnabled(true);
345                        frameeditevbutton.setToolTipText(null);
346                    }
347                }
348            } else {
349                frameeditevbutton.setEnabled(false);
350                frameeditevbutton.setToolTipText(null);
351            }
352            if ( spinnersDirty() ) {
353                framedeletebutton.setEnabled(false);
354                framedeletebutton.setToolTipText("Cannot Delete an edited event or node number");
355            }
356            else {
357                framedeletebutton.setEnabled(true);
358                framedeletebutton.setToolTipText(null);
359            }
360        }
361    }
362
363    public int getEventVal(){
364        return (Integer) numberSpinnerEv.getValue();
365    }
366
367    public int getNodeVal(){
368        return (Integer) numberSpinnernd.getValue();
369    }
370
371    /**
372     * {@inheritDoc}
373     */
374    @Override
375    public String getTitle() {
376
377        if (nodeModel==null){
378            return Bundle.getMessage("NewEvent");
379        }
380
381        if (_node!=null) {
382            StringBuilder title = new StringBuilder();
383            if ( isNewEvent ) {
384                title.append( Bundle.getMessage("NewEvent"))
385                .append(" ");
386            } else {
387                title.append( Bundle.getMessage("EditEvent"))
388                .append(
389                new CbusNameService(_memo).getEventNodeString(_ndEv.getNn(), _ndEv.getEn() ));
390            }
391            title.append("on ")
392            .append( Bundle.getMessage("CbusNode"))
393            .append( _node);
394            return title.toString();
395        } else {
396            return Bundle.getMessage("NewEvent");
397        }
398    }
399
400    /**
401     * {@inheritDoc}
402     */
403    @Override
404    public void tableChanged(TableModelEvent e) {
405        updateButtons();
406    }
407
408    /**
409     * {@inheritDoc}
410     */
411    @Override
412    public void dispose() {
413        if (_node!=null) {
414            _node.removePropertyChangeListener(this);
415        }
416        if ( mainpane != null ){
417            mainpane.clearEditEventFrame();
418        }
419        super.dispose();
420    }
421
422    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusNodeEditEventFrame.class);
423
424}