001package jmri.jmrix.can.cbus.swing.eventrequestmonitor;
002
003import javax.swing.JButton;
004import javax.swing.JCheckBox;
005
006import java.util.ArrayList;
007import java.util.Date;
008
009import jmri.jmrix.can.CanListener;
010import jmri.jmrix.can.CanMessage;
011import jmri.jmrix.can.CanReply;
012import jmri.jmrix.can.CanSystemConnectionMemo;
013import jmri.jmrix.can.cbus.CbusMessage;
014import jmri.jmrix.can.cbus.CbusNameService;
015import jmri.jmrix.can.cbus.CbusOpCodes;
016import jmri.jmrix.can.TrafficController;
017import jmri.jmrix.can.cbus.CbusEvent;
018import jmri.util.swing.TextAreaFIFO;
019import jmri.util.swing.JmriJOptionPane;
020import jmri.util.ThreadingUtil;
021
022/**
023 * Table data model for display of Cbus request events
024 *
025 * @author Steve Young (c) 2018
026 *
027 */
028public class CbusEventRequestDataModel extends javax.swing.table.AbstractTableModel implements CanListener {
029
030    private boolean sessionConfirmDeleteRow=true; // display confirm popup
031    private final int _defaultFeedback= 1;
032    protected int _contype=0; // event table pane console message type
033    protected String _context; // event table pane console text
034    private final int _defaultfeedbackdelay = 4000;
035    private static final int MAX_LINES = 500; // tablefeedback screen log size
036
037    protected ArrayList<CbusEventRequestMonitorEvent> _mainArray;
038    private final CbusNameService nameService;
039    protected TextAreaFIFO tablefeedback;
040    // private CanSystemConnectionMemo _memo;
041    private final TrafficController tc;
042
043    // column order needs to match list in column tooltips
044    static public final int EVENT_COLUMN = 0;
045    static public final int NODE_COLUMN = 1;
046    static public final int NAME_COLUMN = 2;
047    static public final int LATEST_TIMESTAMP_COLUMN = 3;
048    static public final int STATUS_REQUEST_BUTTON_COLUMN = 4;
049    static public final int LASTFEEDBACK_COLUMN = 5;
050    static public final int FEEDBACKOUTSTANDING_COLUMN = 6;
051    static public final int FEEDBACKREQUIRED_COLUMN = 7;
052    static public final int FEEDBACKTIMEOUT_COLUMN = 8;
053    static public final int FEEDBACKEVENT_COLUMN = 9;
054    static public final int FEEDBACKNODE_COLUMN = 10;
055    static public final int DELETE_BUTTON_COLUMN = 11;
056
057    static public final int MAX_COLUMN = 12;
058
059    CbusEventRequestDataModel(CanSystemConnectionMemo memo, int row, int column) {
060
061        _mainArray = new ArrayList<>();
062        tablefeedback = new TextAreaFIFO(MAX_LINES);
063        // _memo = memo;
064        tc = memo.getTrafficController();
065        addTc(tc);
066        nameService = new CbusNameService(memo);
067    }
068
069    // order needs to match column list top of dtabledatamodel
070    static final String[] columnToolTips = {
071        Bundle.getMessage("EventColTip"),
072        Bundle.getMessage("NodeColTip"),
073        Bundle.getMessage("NameColTip"),
074        Bundle.getMessage("ColumnLastHeard") + Bundle.getMessage("TypeColTip"),
075        Bundle.getMessage("ColumnRequestStatusTip"),
076        Bundle.getMessage("FBLastTip"),
077        Bundle.getMessage("FBOutstandingTip"),
078        Bundle.getMessage("FBNumTip"),
079        Bundle.getMessage("FBTimeoutTip"),
080        Bundle.getMessage("FBEventTip"),
081        Bundle.getMessage("FBNodeTip"),
082        Bundle.getMessage("ColumnEventDeleteTip")
083
084    }; // Length = number of items in array should (at least) match number of columns
085
086    /**
087     * Return the number of rows to be displayed.
088     */
089    @Override
090    public int getRowCount() {
091        return _mainArray.size();
092    }
093
094    @Override
095    public int getColumnCount() {
096        return MAX_COLUMN;
097    }
098
099    /**
100     * Returns String of column name from column int
101     * used in table header
102     * @param col int col number
103     */
104    @Override
105    public String getColumnName(int col) { // not in any order
106        switch (col) {
107            case NODE_COLUMN:
108                return Bundle.getMessage("CbusNode");
109            case NAME_COLUMN:
110                return Bundle.getMessage("ColumnName");
111            case EVENT_COLUMN:
112                return Bundle.getMessage("CbusEvent");
113            case DELETE_BUTTON_COLUMN:
114                return Bundle.getMessage("ColumnEventDelete");
115            case STATUS_REQUEST_BUTTON_COLUMN:
116                return Bundle.getMessage("ColumnStatusRequest");
117            case LATEST_TIMESTAMP_COLUMN:
118                return Bundle.getMessage("ColumnLastHeard");
119            case LASTFEEDBACK_COLUMN:
120                return Bundle.getMessage("FBLast");
121            case FEEDBACKREQUIRED_COLUMN:
122                return Bundle.getMessage("FBRequired");
123            case FEEDBACKOUTSTANDING_COLUMN:
124                return Bundle.getMessage("FBOutstanding");
125            case FEEDBACKEVENT_COLUMN:
126                return Bundle.getMessage("FBEvent");
127            case FEEDBACKNODE_COLUMN:
128                return Bundle.getMessage("FBNode");
129            case FEEDBACKTIMEOUT_COLUMN:
130                return Bundle.getMessage("FBTimeout");
131            default:
132                return "unknown"; // NOI18N
133        }
134    }
135
136    /**
137    * Returns column class type.
138    * {@inheritDoc}
139    */
140    @Override
141    public Class<?> getColumnClass(int col) {
142        switch (col) {
143            case EVENT_COLUMN:
144            case NODE_COLUMN:
145            case FEEDBACKREQUIRED_COLUMN:
146            case FEEDBACKOUTSTANDING_COLUMN:
147            case FEEDBACKEVENT_COLUMN:
148            case FEEDBACKNODE_COLUMN:
149            case FEEDBACKTIMEOUT_COLUMN:
150                return Integer.class;
151            case NAME_COLUMN:
152                return String.class;
153            case DELETE_BUTTON_COLUMN:
154            case STATUS_REQUEST_BUTTON_COLUMN:
155                return JButton.class;
156            case LATEST_TIMESTAMP_COLUMN:
157                return Date.class;
158            case LASTFEEDBACK_COLUMN:
159                return Enum.class;
160            default:
161                return null;
162        }
163    }
164
165    /**
166    * Boolean return to edit table cell or not
167    * {@inheritDoc}
168    * @return boolean
169    */
170    @Override
171    public boolean isCellEditable(int row, int col) {
172        switch (col) {
173            case DELETE_BUTTON_COLUMN:
174            case STATUS_REQUEST_BUTTON_COLUMN:
175            case FEEDBACKREQUIRED_COLUMN:
176            case FEEDBACKEVENT_COLUMN:
177            case FEEDBACKNODE_COLUMN:
178            case FEEDBACKTIMEOUT_COLUMN:
179                return true;
180            default:
181                return false;
182        }
183    }
184
185     /**
186     * Return table values
187     * @param row int row number
188     * @param col int col number
189     */
190    @Override
191    public Object getValueAt(int row, int col) {
192        switch (col) {
193            case NODE_COLUMN:
194                return  _mainArray.get(row).getNn();
195            case EVENT_COLUMN:
196                return _mainArray.get(row).getEn();
197            case NAME_COLUMN:
198                return nameService.getEventName(_mainArray.get(row).getNn(),_mainArray.get(row).getEn() );
199            case STATUS_REQUEST_BUTTON_COLUMN:
200                return Bundle.getMessage("StatusButton");
201            case DELETE_BUTTON_COLUMN:
202                return Bundle.getMessage("ButtonDelete");
203            case LATEST_TIMESTAMP_COLUMN:
204                return _mainArray.get(row).getDate();
205            case LASTFEEDBACK_COLUMN:
206                return _mainArray.get(row).getLastFb();
207            case FEEDBACKREQUIRED_COLUMN:
208                return _mainArray.get(row).getFeedbackTotReqd();
209            case FEEDBACKOUTSTANDING_COLUMN:
210                return _mainArray.get(row).getFeedbackOutstanding();
211            case FEEDBACKEVENT_COLUMN:
212                return _mainArray.get(row).getExtraEvent();
213            case FEEDBACKNODE_COLUMN:
214                return _mainArray.get(row).getExtraNode();
215            case FEEDBACKTIMEOUT_COLUMN:
216                return _mainArray.get(row).getFeedbackTimeout();
217            default:
218                return null;
219        }
220    }
221
222    /**
223     * Capture new comments or node names.
224     * Button events
225     * @param value object value
226     * @param row int row number
227     * @param col int col number
228     */
229    @Override
230    public void setValueAt(Object value, int row, int col) {
231        // log.debug("427 set valueat called row: {} col: {}", row, col);
232        switch (col) {
233            case DELETE_BUTTON_COLUMN:
234                buttonDeleteClicked(row);
235                break;
236            case STATUS_REQUEST_BUTTON_COLUMN:
237                _mainArray.get(row).sendEvent(CbusEventRequestMonitorEvent.EvState.REQUEST); // gui updates from outgoing msg
238                break;
239            case LATEST_TIMESTAMP_COLUMN:
240                _mainArray.get(row).setDate( new Date() );
241                updateGui(row, col);
242                break;
243            case FEEDBACKREQUIRED_COLUMN:
244                _mainArray.get(row).setFeedbackTotReqd( (int) value );
245                updateGui(row, col);
246                break;
247            case FEEDBACKOUTSTANDING_COLUMN:
248                _mainArray.get(row).setFeedbackOutstanding( (Integer) value );
249                updateGui(row, col);
250                break;
251            case FEEDBACKEVENT_COLUMN:
252                _mainArray.get(row).setExtraEvent( (int) value );
253                updateGui(row, col);
254                break;
255            case FEEDBACKNODE_COLUMN:
256                _mainArray.get(row).setExtraNode( (int) value );
257                updateGui(row, col);
258                break;
259            case FEEDBACKTIMEOUT_COLUMN:
260                _mainArray.get(row).setFeedbackTimeout( (int) value );
261                updateGui(row, col);
262                break;
263            case LASTFEEDBACK_COLUMN:
264                _mainArray.get(row).setLastFb( (CbusEventRequestMonitorEvent.FbState) value );
265                updateGui(row, col);
266                break;
267            default:
268                break;
269        }
270    }
271
272    private void updateGui(int row, int col){
273        ThreadingUtil.runOnGUIEventually( ()->{
274            fireTableCellUpdated(row, col);
275        });
276    }
277
278    // outgoing cbus message
279    // or incoming CanReply
280    @Override
281    public void message(CanMessage m) {
282        if ( m.extendedOrRtr() ) {
283            return;
284        }
285        int opc = CbusMessage.getOpcode(m);
286        if (CbusOpCodes.isEventRequest(opc)) {
287            processEvRequest( CbusMessage.getNodeNumber(m) , CbusMessage.getEvent(m) );
288        }
289        else if (CbusOpCodes.isEventNotRequest(opc)) {
290            processEvent( CbusMessage.getNodeNumber(m) , CbusMessage.getEvent(m) );
291        }
292    }
293
294    // incoming cbus message
295    // handled the same as outgoing
296    @Override
297    public void reply(CanReply r) {
298        if ( r.extendedOrRtr() ) {
299            return;
300        }
301        CanMessage m = new CanMessage(r);
302        message(m);
303    }
304
305    // called when event heard as CanReply / CanMessage
306    private void processEvent( int nn, int en ){
307
308        int existingRow = eventRow( nn, en);
309        int fbRow = extraFeedbackRow( nn, en);
310        if ( existingRow > -1 ) {
311            _mainArray.get(existingRow).setResponseReceived();
312            setValueAt(1, existingRow, LATEST_TIMESTAMP_COLUMN);
313        }
314        else if ( fbRow > -1 ){
315            _mainArray.get(fbRow).setResponseReceived();
316        }
317    }
318
319    // called when request heard as CanReply / CanMessage
320    private void processEvRequest( int nn, int en ) {
321
322        int existingRow = eventRow( nn, en);
323        if (existingRow<0) {
324            addEvent(nn,en,CbusEventRequestMonitorEvent.EvState.REQUEST,null);
325        }
326        existingRow = eventRow( nn, en);
327        _mainArray.get(existingRow).setRequestReceived();
328
329    }
330
331    protected int eventRow(int nn, int en) {
332        return _mainArray.indexOf(new CbusEvent(nn,en));
333    }
334
335    protected int extraFeedbackRow(int nn, int en) {
336        for (int i = 0; i < getRowCount(); i++) {
337            if (_mainArray.get(i).matchesFeedback(nn, en)) {
338                return i;
339            }
340        }
341        return -1;
342    }
343
344    public void addEvent(int node, int event, CbusEventRequestMonitorEvent.EvState state, Date timestamp) {
345
346        CbusEventRequestMonitorEvent newmonitor = new CbusEventRequestMonitorEvent(
347            node, event, state, timestamp, _defaultfeedbackdelay,
348            _defaultFeedback, this );
349
350        _mainArray.add(newmonitor);
351
352        ThreadingUtil.runOnGUI( ()->{ fireTableRowsInserted((getRowCount()-1), (getRowCount()-1)); });
353        addToLog(1,newmonitor.toString() + Bundle.getMessage("AddedToTable"));
354    }
355
356    /**
357     * Remove Row from table
358     * @see #buttonDeleteClicked
359     * @param row int row number
360     */
361    private void removeRow(int row) {
362        _context = _mainArray.get(row).toString() + Bundle.getMessage("TableConfirmDelete");
363        _mainArray.remove(row);
364        ThreadingUtil.runOnGUI( ()->{ fireTableRowsDeleted(row,row); });
365        addToLog(3,_context);
366    }
367
368    /**
369     * Delete Button Clicked
370     * See whether to display confirm popup
371     * @see #removeRow
372     * @param row int row number
373     */
374    private void buttonDeleteClicked(int row) {
375        if (sessionConfirmDeleteRow) {
376            // confirm deletion with the user
377            JCheckBox checkbox = new JCheckBox(Bundle.getMessage("PopupSessionConfirmDel"));
378            String message = Bundle.getMessage("DelConfirmOne") + "\n"
379            + Bundle.getMessage("DelConfirmTwo");
380            Object[] params = {message, checkbox};
381
382            if (JmriJOptionPane.OK_OPTION == JmriJOptionPane.showConfirmDialog(
383                    null, params, Bundle.getMessage("DelEvPopTitle"),
384                    JmriJOptionPane.OK_CANCEL_OPTION,
385                    JmriJOptionPane.WARNING_MESSAGE)) {
386                    boolean dontShow = checkbox.isSelected();
387                    if (dontShow) {
388                        sessionConfirmDeleteRow=false;
389                    }
390                    removeRow(row);
391            }
392        } else {
393            // no need to show warning, just delete
394            removeRow(row);
395        }
396    }
397
398    /**
399     * Add to Event Table Console Log
400     * @param cbuserror int
401     * @param cbustext String console message
402     */
403    public void addToLog(int cbuserror, String cbustext){
404        ThreadingUtil.runOnGUI( ()->{
405            if (cbuserror==3) {
406            tablefeedback.append ("\n * * * * * * * * * * * * * * * * * * * * * * " + cbustext);
407            } else {
408                tablefeedback.append( "\n"+cbustext);
409            }
410        });
411    }
412
413    /**
414     * disconnect from the CBUS
415     */
416    public void dispose() {
417        // eventTable.removeAllElements();
418        // eventTable = null;
419        for (int i = 0; i < getRowCount(); i++) {
420            _mainArray.get(i).stopTheTimer();
421        }
422        _mainArray = null;
423
424        tablefeedback.dispose();
425        tc.removeCanListener(this);
426
427    }
428
429    protected TextAreaFIFO tablefeedback(){
430        return tablefeedback;
431    }
432
433    // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusEventRequestDataModel.class);
434
435}