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.swing.JmriJOptionPane;
024import jmri.util.swing.JSpinnerUtil;
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        JSpinnerUtil.setCommitsOnValidEdit(numberSpinnernd, true);
106
107        numberSpinnerEv = new JSpinner(new SpinnerNumberModel(Math.max(0,_ndEv.getEn()), 0, 65535, 1));
108        JSpinner.NumberEditor neditor = new JSpinner.NumberEditor(numberSpinnernd, "#");
109        numberSpinnernd.setEditor(neditor);
110        JSpinnerUtil.setCommitsOnValidEdit(numberSpinnerEv, true);
111        
112        infoPane = new JPanel();
113        infoPane.setLayout(new BorderLayout() );        
114        
115        JPanel pageStartPanel = new JPanel();
116        pageStartPanel.setLayout(new BoxLayout(pageStartPanel, BoxLayout.Y_AXIS));
117        
118        JPanel buttonPanel = new JPanel();
119        
120        buttonPanel.add(frameCopyButton);
121        buttonPanel.add(frameResetButton);
122        buttonPanel.add(framedeletebutton);
123        
124        JPanel spinnerPanel= new JPanel(); // row container
125
126        spinnerPanel.add(new JLabel(Bundle.getMessage("CbusNode"), JLabel.RIGHT));
127        spinnerPanel.add(numberSpinnernd);
128        spinnerPanel.add(new JLabel(Bundle.getMessage("CbusEvent"), JLabel.RIGHT));
129        spinnerPanel.add(numberSpinnerEv);
130        
131        spinnerPanel.add(framenewevbutton);
132        spinnerPanel.add(frameeditevbutton);
133        
134        JPanel evNamePanel= new JPanel(); // row container
135        ndEvNameLabel = new JLabel("");
136        evNamePanel.add(ndEvNameLabel);
137        
138        pageStartPanel.add(spinnerPanel);
139        pageStartPanel.add(evNamePanel);
140        pageStartPanel.add( buttonPanel);
141        pageStartPanel.add(new JSeparator(SwingConstants.HORIZONTAL));
142        
143        pageStartPanel.validate();
144        
145        JPanel generic = new JPanel();
146        
147        generic.setLayout( new BorderLayout() );
148        
149        CbusNodeSingleEventEditTablePane genericEVTable = new CbusNodeSingleEventEditTablePane(singleEVModel);
150        genericEVTable.initComponents(_memo, mainpane);
151        generic.add( genericEVTable );
152        
153        tabbedPane = new JTabbedPane();
154        
155        tabbedPane.addTab(("Template"), null);
156        tabbedPane.addTab(("Generic"), generic);
157        
158        tabbedPane.setEnabledAt(0,false);
159        tabbedPane.setSelectedIndex(1);
160        
161        infoPane.add(pageStartPanel, BorderLayout.PAGE_START);
162        infoPane.add(tabbedPane, BorderLayout.CENTER);
163        
164        this.add(infoPane);
165        
166        this.setPreferredSize(new Dimension(500, 300));
167        
168        this.pack();
169        this.setResizable(true);
170        
171        this.validate();
172        this.repaint();
173        
174        this.setVisible(true);
175        this.toFront();
176        
177        updateButtons();
178        
179        frameResetButton.addActionListener((ActionEvent e) -> {
180            singleEVModel.resetnewEVs();
181            numberSpinnernd.setValue(Math.max(0,_ndEv.getNn() ) );
182            numberSpinnerEv.setValue(Math.max(1,_ndEv.getEn() ) );
183        });
184        
185        
186        ActionListener copyEvClicked = ae -> {
187            log.debug("copy button");
188            isNewEvent=true;
189            updateButtons();
190        };        
191        frameCopyButton.addActionListener(copyEvClicked);
192        
193        // node will check for other nodes in learn mode
194        ActionListener newEvClicked = ae -> {
195            busy_dialog = new jmri.util.swing.BusyDialog(this, "Teaching Node", false);
196            busy_dialog.start();
197         //   setEnabled(false);
198            jmri.util.ThreadingUtil.runOnLayout( ()->{
199                singleEVModel.passNewEvToNode(this);
200            });
201        };
202        framenewevbutton.addActionListener(newEvClicked);
203        
204        ActionListener deleteClicked = ae -> {
205            if (_node == null ){
206                return;
207            }
208            int response = JmriJOptionPane.showConfirmDialog(
209                this,
210                Bundle.getMessage("NdDelEvConfrm",
211                    new CbusNameService(_memo).getEventNodeString(_ndEv.getNn(), _ndEv.getEn() ), _node ),
212                Bundle.getMessage("DelEvPopTitle"), 
213                JmriJOptionPane.YES_NO_OPTION,         
214                JmriJOptionPane.ERROR_MESSAGE);
215            if (response == JmriJOptionPane.YES_OPTION) {
216            
217                busy_dialog = new jmri.util.swing.BusyDialog(this, "Deleting Event", false);
218                busy_dialog.start();
219              //  setEnabled(false);
220                jmri.util.ThreadingUtil.runOnLayout( ()->{
221                    _node.getNodeEventManager().deleteEvOnNode(_ndEv.getNn(), _ndEv.getEn() );
222                });
223            }
224        };
225        framedeletebutton.addActionListener(deleteClicked);
226        
227        ActionListener editEvClicked = ae -> {
228            busy_dialog = new jmri.util.swing.BusyDialog(this, "Teaching Node", false);
229            busy_dialog.start();
230         //   setEnabled(false);
231            jmri.util.ThreadingUtil.runOnLayout( ()->{
232                singleEVModel.passEditEvToNode(this);
233            });
234        };
235        frameeditevbutton.addActionListener(editEvClicked);        
236        
237        numberSpinnerEv.addChangeListener((ChangeEvent e) -> {
238            updateButtons();
239        });
240        
241        numberSpinnernd.addChangeListener((ChangeEvent e) -> {
242            updateButtons();
243        });        
244        
245    }
246    
247    public boolean spinnersDirty(){
248        return ( _ndEv.getNn() != getNodeVal() ) || ( _ndEv.getEn() != getEventVal() );
249    }
250    
251    private void notifyLearnEvoutcome( String message) {
252        
253        _ndEv.setNn( getNodeVal() );
254        _ndEv.setEn( getEventVal() );
255        _ndEv.setEvArr( Arrays.copyOf(
256            singleEVModel.newEVs,
257            singleEVModel.newEVs.length) );
258        singleEVModel.fireTableDataChanged();
259        
260        updateButtons();
261        busy_dialog.finish();
262        busy_dialog = null;
263        
264        if (!message.isEmpty() ) {
265            JmriJOptionPane.showMessageDialog( this, 
266            Bundle.getMessage("NdEvVarWriteError"), Bundle.getMessage("WarningTitle"),
267            JmriJOptionPane.ERROR_MESSAGE);
268        }
269    }
270    
271    private void notifyDeleteEvoutcome(String message) {
272        busy_dialog.finish();
273        busy_dialog = null;
274        updateButtons();
275        if (!message.isEmpty()) {
276            JmriJOptionPane.showMessageDialog( this, 
277            message, Bundle.getMessage("WarningTitle"),
278            JmriJOptionPane.ERROR_MESSAGE);
279        }
280        this.dispose();
281    }
282    
283    /** {@inheritDoc} */
284    @Override
285    public void propertyChange(PropertyChangeEvent ev){
286        
287        if (ev.getPropertyName().equals("DELETEEVCOMPLETE")) {
288            jmri.util.ThreadingUtil.runOnGUIEventually( ()->{
289                notifyDeleteEvoutcome(ev.getNewValue().toString());
290            });
291        }
292        if (ev.getPropertyName().equals("ADDEVCOMPLETE")) {
293            jmri.util.ThreadingUtil.runOnGUIEventually( ()->{
294                notifyLearnEvoutcome(ev.getNewValue().toString());
295            });
296        }
297    }
298    
299    // called on startup + when number spinners changed
300    private void updateButtons(){
301       
302        setTitle( getTitle() );
303        
304        ndEvNameLabel.setText("<html><div style='text-align: center;'>" + 
305        new CbusNameService(_memo).getEventNodeString(getNodeVal(), getEventVal() )
306        + "</div></html>");
307        
308        frameResetButton.setEnabled(singleEVModel.isTableDirty() || spinnersDirty());
309        
310        if (_node == null){
311            return;
312        }
313        framenewevbutton.setVisible(isNewEvent);
314        frameeditevbutton.setVisible(!isNewEvent);
315        framedeletebutton.setVisible(!isNewEvent);
316        frameCopyButton.setVisible(!isNewEvent);
317        
318        if (isNewEvent) {
319            
320            if ( _node.getNodeEventManager().getNodeEvent(getNodeVal(),getEventVal() ) == null ) {
321                
322                framenewevbutton.setEnabled(true);
323                framenewevbutton.setToolTipText(null);
324                
325            } else {
326                framenewevbutton.setEnabled(false);
327                framenewevbutton.setToolTipText("Event Already on Node");
328            }
329            
330        }
331        else { // not new event
332            if ( spinnersDirty() || singleEVModel.isTableDirty() ){
333                if ( _node.getNodeEventManager().getNodeEvent(getNodeVal(),getEventVal() ) == null ) {
334                    frameeditevbutton.setEnabled(true);
335                    frameeditevbutton.setToolTipText(null);
336                } else {
337                    if ( spinnersDirty() ) {
338                        frameeditevbutton.setEnabled(false);
339                        frameeditevbutton.setToolTipText("Event Already on Node");
340                    }
341                    else {
342                        frameeditevbutton.setEnabled(true);
343                        frameeditevbutton.setToolTipText(null);
344                    }
345                }
346            } else {
347                frameeditevbutton.setEnabled(false);
348                frameeditevbutton.setToolTipText(null);
349            }
350            if ( spinnersDirty() ) {
351                framedeletebutton.setEnabled(false);
352                framedeletebutton.setToolTipText("Cannot Delete an edited event or node number");
353            }
354            else {
355                framedeletebutton.setEnabled(true);
356                framedeletebutton.setToolTipText(null);
357            }
358        }
359    }
360    
361    public int getEventVal(){
362        return (Integer) numberSpinnerEv.getValue();
363    }
364
365    public int getNodeVal(){
366        return (Integer) numberSpinnernd.getValue();
367    }
368
369    /**
370     * {@inheritDoc}
371     */
372    @Override
373    public String getTitle() {
374        
375        if (nodeModel==null){
376            return Bundle.getMessage("NewEvent");
377        }
378        
379        if (_node!=null) {
380            StringBuilder title = new StringBuilder();
381            if ( isNewEvent ) {
382                title.append( Bundle.getMessage("NewEvent"))
383                .append(" ");
384            } else {
385                title.append( Bundle.getMessage("EditEvent"))
386                .append(
387                new CbusNameService(_memo).getEventNodeString(_ndEv.getNn(), _ndEv.getEn() ));
388            }
389            title.append("on ")
390            .append( Bundle.getMessage("CbusNode"))
391            .append( _node);
392            return title.toString();
393        } else {
394            return Bundle.getMessage("NewEvent");
395        }
396    }
397
398    /**
399     * {@inheritDoc}
400     */
401    @Override
402    public void tableChanged(TableModelEvent e) {
403        updateButtons();
404    }
405    
406    /**
407     * {@inheritDoc}
408     */
409    @Override
410    public void dispose() {
411        if (_node!=null) {
412            _node.removePropertyChangeListener(this);
413        }
414        if ( mainpane != null ){
415            mainpane.clearEditEventFrame();
416        }
417        super.dispose();
418    }
419
420    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusNodeEditEventFrame.class);
421
422}