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