001package jmri.jmrix.loconet.slotmon;
002
003import java.time.Instant;
004import java.time.ZoneId;
005import java.time.ZonedDateTime;
006import java.time.format.DateTimeFormatter;
007
008import javax.swing.JButton;
009import javax.swing.JCheckBox;
010import javax.swing.JLabel;
011import javax.swing.JTable;
012import javax.swing.JTextField;
013
014import jmri.Throttle;
015import jmri.jmrix.loconet.LnConstants;
016import jmri.jmrix.loconet.LocoNetMessage;
017import jmri.jmrix.loconet.LocoNetSlot;
018import jmri.jmrix.loconet.LocoNetSystemConnectionMemo;
019import jmri.jmrix.loconet.SlotListener;
020import jmri.jmrix.loconet.SlotMapEntry.SlotType;
021import jmri.util.StringUtil;
022import jmri.util.swing.JmriJOptionPane;
023
024/**
025 * Table data model for display of slot manager contents.
026 *
027 * @author Bob Jacobsen Copyright (C) 2001
028 * @author Jeffrey Machacek 2013
029 */
030public class SlotMonDataModel extends javax.swing.table.AbstractTableModel implements SlotListener {
031
032/**
033 * ColumnNumber
034 * This enum defines the default column order.
035 * F0COLUMN thru F28COLUMN must be kept in a block and in order.
036 */
037    enum ColumnNumber {SLOTCOLUMN,
038    ESTOPCOLUMN,
039    ADDRCOLUMN,
040    SPDCOLUMN,
041    TYPECOLUMN,
042    STATCOLUMN,
043    LASTUPDATE,
044    DISPCOLUMN,
045    CONSCOLUMN,
046    CONSISTADDRESS,
047    THROTCOLUMN,
048    JMRITHROTTLECOUNT,
049    DIRCOLUMN,
050    F0COLUMN,
051    F1COLUMN,
052    F2COLUMN,
053    F3COLUMN,
054    F4COLUMN,
055    F5COLUMN,
056    F6COLUMN,
057    F7COLUMN,
058    F8COLUMN,
059    F9COLUMN,
060    F10COLUMN,
061    F11COLUMN,
062    F12COLUMN,
063    F13COLUMN,
064    F14COLUMN,
065    F15COLUMN,
066    F16COLUMN,
067    F17COLUMN,
068    F18COLUMN,
069    F19COLUMN,
070    F20COLUMN,
071    F21COLUMN,
072    F22COLUMN,
073    F23COLUMN,
074    F24COLUMN,
075    F25COLUMN,
076    F26COLUMN,
077    F27COLUMN,
078    F28COLUMN,
079    MAXCOLUMN}
080
081    private int numRows = 128;
082    private int columns;
083
084    private final transient LocoNetSystemConnectionMemo memo;
085
086    SlotMonDataModel(int row, LocoNetSystemConnectionMemo memo) {
087        this.memo = memo;
088
089        // number of columns
090        this.columns = ColumnNumber.MAXCOLUMN.ordinal();
091        // set number of rows;
092        numRows = row;
093
094        // connect to SlotManager for updates
095        memo.getSlotManager().addSlotListener(SlotMonDataModel.this);
096
097        // start update process
098        memo.getSlotManager().update();
099    }
100
101    /**
102     * Forces a refresh of the slots
103     */
104    public void refreshSlots() {
105        memo.getSlotManager().update();
106    }
107
108    /**
109     * Return the number of rows to be displayed. This can vary depending on
110     * whether only active rows are displayed, and whether the system slots
111     * should be displayed.
112     * <p>
113     * This should probably use a local cache instead of counting/searching each
114     * time.
115     *
116     * @return the number of rows
117     */
118    @Override
119    public int getRowCount() {
120        return numRows;
121    }
122
123    @Override
124    public int getColumnCount() {
125        return columns;
126    }
127
128    @Override
129    public String getColumnName(int col) {
130        ColumnNumber colorder = ColumnNumber.values()[col];
131        switch (colorder) {
132            case SLOTCOLUMN:
133                return Bundle.getMessage("SlotCol");
134            case ESTOPCOLUMN:
135                return Bundle.getMessage("ButtonEstop");     // no heading, as button is clear
136            case ADDRCOLUMN:
137                return Bundle.getMessage("AddressCol");
138            case SPDCOLUMN:
139                return Bundle.getMessage("SpeedCol");
140            case TYPECOLUMN:
141                return Bundle.getMessage("StatusCol");
142            case STATCOLUMN:
143                return Bundle.getMessage("UseCol");
144            case CONSCOLUMN:
145                return Bundle.getMessage("ConsistedCol");
146            case CONSISTADDRESS:
147                return Bundle.getMessage("ConsistAddress");
148            case DIRCOLUMN:
149                return Bundle.getMessage("DirectionCol");
150            case DISPCOLUMN:
151                return Bundle.getMessage("ButtonRelease");
152            case F0COLUMN:
153                return Throttle.getFunctionString(0);
154            case F1COLUMN:
155                return Throttle.getFunctionString(1);
156            case F2COLUMN:
157                return Throttle.getFunctionString(2);
158            case F3COLUMN:
159                return Throttle.getFunctionString(3);
160            case F4COLUMN:
161                return Throttle.getFunctionString(4);
162            case F5COLUMN:
163                return Throttle.getFunctionString(5);
164            case F6COLUMN:
165                return Throttle.getFunctionString(6);
166            case F7COLUMN:
167                return Throttle.getFunctionString(7);
168            case F8COLUMN:
169                return Throttle.getFunctionString(8);
170            case F9COLUMN:
171                return Throttle.getFunctionString(9);
172            case F10COLUMN:
173                return Throttle.getFunctionString(10);
174            case F11COLUMN:
175                return Throttle.getFunctionString(11);
176            case F12COLUMN:
177                return Throttle.getFunctionString(12);
178            case F13COLUMN:
179                return Throttle.getFunctionString(13);
180            case F14COLUMN:
181                return Throttle.getFunctionString(14);
182            case F15COLUMN:
183                return Throttle.getFunctionString(15);
184            case F16COLUMN:
185                return Throttle.getFunctionString(16);
186            case F17COLUMN:
187                return Throttle.getFunctionString(17);
188            case F18COLUMN:
189                return Throttle.getFunctionString(18);
190            case F19COLUMN:
191                return Throttle.getFunctionString(19);
192            case F20COLUMN:
193                return Throttle.getFunctionString(20);
194            case F21COLUMN:
195                return Throttle.getFunctionString(21);
196            case F22COLUMN:
197                return Throttle.getFunctionString(22);
198            case F23COLUMN:
199                return Throttle.getFunctionString(23);
200            case F24COLUMN:
201                return Throttle.getFunctionString(24);
202            case F25COLUMN:
203                return Throttle.getFunctionString(25);
204            case F26COLUMN:
205                return Throttle.getFunctionString(26);
206            case F27COLUMN:
207                return Throttle.getFunctionString(27);
208            case F28COLUMN:
209                return Throttle.getFunctionString(28);
210            case THROTCOLUMN:
211                return Bundle.getMessage("ThrottleIDCol");
212            case JMRITHROTTLECOUNT:
213                return Bundle.getMessage("JMRIThrottleCount");
214            case LASTUPDATE:
215                return Bundle.getMessage("LastUpdate");
216            default:
217                return "unknown"; // NOI18N
218        }
219    }
220
221    @Override
222    public Class<?> getColumnClass(int col) {
223        ColumnNumber colorder = ColumnNumber.values()[col];
224        switch (colorder) {
225            case SLOTCOLUMN:
226            case ADDRCOLUMN:
227            case CONSISTADDRESS:
228            case JMRITHROTTLECOUNT:
229                return Integer.class;
230            case SPDCOLUMN:
231            case TYPECOLUMN:
232            case STATCOLUMN:
233            case CONSCOLUMN:
234            case DIRCOLUMN:
235            case THROTCOLUMN:
236            case LASTUPDATE:
237                return String.class;
238            case ESTOPCOLUMN:
239            case DISPCOLUMN:
240                return JButton.class;
241            case F0COLUMN:
242            case F1COLUMN:
243            case F2COLUMN:
244            case F3COLUMN:
245            case F4COLUMN:
246            case F5COLUMN:
247            case F6COLUMN:
248            case F7COLUMN:
249            case F8COLUMN:
250            case F9COLUMN:
251            case F10COLUMN:
252            case F11COLUMN:
253            case F12COLUMN:
254            case F13COLUMN:
255            case F14COLUMN:
256            case F15COLUMN:
257            case F16COLUMN:
258            case F17COLUMN:
259            case F18COLUMN:
260            case F19COLUMN:
261            case F20COLUMN:
262            case F21COLUMN:
263            case F22COLUMN:
264            case F23COLUMN:
265            case F24COLUMN:
266            case F25COLUMN:
267            case F26COLUMN:
268            case F27COLUMN:
269            case F28COLUMN:
270                return Boolean.class;
271            default:
272                return null;
273        }
274    }
275
276    @Override
277    public boolean isCellEditable(int row, int col) {
278        LocoNetSlot s = memo.getSlotManager().slot(row);
279        ColumnNumber colorder = ColumnNumber.values()[col];
280        switch (colorder) {
281            case ESTOPCOLUMN:
282            case DISPCOLUMN:
283            case F0COLUMN:
284            case F1COLUMN:
285            case F2COLUMN:
286            case F3COLUMN:
287            case F4COLUMN:
288            case F5COLUMN:
289            case F6COLUMN:
290            case F7COLUMN:
291            case F8COLUMN:
292            case F9COLUMN:
293            case F10COLUMN:
294            case F11COLUMN:
295            case F12COLUMN:
296            case F13COLUMN:
297            case F14COLUMN:
298            case F15COLUMN:
299            case F16COLUMN:
300            case F17COLUMN:
301            case F18COLUMN:
302            case F19COLUMN:
303            case F20COLUMN:
304            case F21COLUMN:
305            case F22COLUMN:
306            case F23COLUMN:
307            case F24COLUMN:
308            case F25COLUMN:
309            case F26COLUMN:
310            case F27COLUMN:
311            case F28COLUMN:
312                // only loco slots to be marked writeable only, system slot are read only
313                return !s.isSystemSlot();
314            default:
315                return false;
316        }
317    }
318
319    @Override
320    public Object getValueAt(int row, int col) {
321        LocoNetSlot s = memo.getSlotManager().slot(row);
322        String t;
323        if (s == null) {
324            log.error("slot pointer was null for slot row: {} col: {}", row, col);
325            return null;
326        }
327
328        ColumnNumber colorder = ColumnNumber.values()[col];
329        switch (colorder) {
330            case SLOTCOLUMN:  // slot number
331                return row;
332            case ESTOPCOLUMN:
333                return Bundle.getMessage("ButtonEstop"); // will be name of button in default GUI
334            case ADDRCOLUMN:
335                return s.locoAddr();
336            case SPDCOLUMN:
337                switch (s.consistStatus()) {
338                    case LnConstants.CONSIST_TOP:
339                    case LnConstants.CONSIST_NO:
340                        if (s.speed() == 1) {
341                            t = "(estop) 1";
342                        } else {
343                            t = "          " + s.speed();
344                        }
345                        return t.substring(t.length() - 9, t.length()); // 9 comes from length of the "(estop)" prefix
346                    case LnConstants.CONSIST_MID:
347                    case LnConstants.CONSIST_SUB:
348                        return Bundle.getMessage("SlotSpeedConsist");
349                    default:
350                        return Bundle.getMessage("StateError");
351                }
352            case TYPECOLUMN:
353                switch (s.decoderType()) {
354                    case LnConstants.DEC_MODE_128A:
355                        return "128 step adv";
356                    case LnConstants.DEC_MODE_28A:
357                        return " 28 step adv";
358                    case LnConstants.DEC_MODE_128:
359                        return "128 step";
360                    case LnConstants.DEC_MODE_14:
361                        return " 14 step";
362                    case LnConstants.DEC_MODE_28TRI:
363                        return " 28 step trinary";
364                    case LnConstants.DEC_MODE_28:
365                        return " 28 step";
366                    default:
367                        return Bundle.getMessage("StateUnknown");
368                }
369            case STATCOLUMN:
370                switch (s.slotStatus()) {
371                    case LnConstants.LOCO_IN_USE:
372                        return Bundle.getMessage("StateInUse");
373                    case LnConstants.LOCO_IDLE:
374                        return Bundle.getMessage("StateIdle");
375                    case LnConstants.LOCO_COMMON:
376                        return Bundle.getMessage("StateCommon");
377                    case LnConstants.LOCO_FREE:
378                        return Bundle.getMessage("StateFree");
379                    default:
380                        return Bundle.getMessage("StateError");
381                }
382            case CONSCOLUMN:
383                if (s.isSystemSlot()) {
384                    // makes no sense to decode
385                    return Bundle.getMessage("SlotDataNotApplicable");
386                }
387                switch (s.consistStatus()) {
388                    case LnConstants.CONSIST_MID:
389                        if (s.getProtocol() == LnConstants.LOCONETPROTOCOL_TWO) {
390                            return Bundle.getMessage("SlotConsistMidX", s.getLeadSlot());
391                        }
392                        t = Bundle.getMessage("SlotConsistMidX", s.speed());
393                        return t;
394                    case LnConstants.CONSIST_TOP:
395                        return Bundle.getMessage("SlotConsistTop");
396                    case LnConstants.CONSIST_SUB:
397                        if (s.getProtocol() == LnConstants.LOCONETPROTOCOL_TWO) {
398                            return Bundle.getMessage("SlotConsistSubX", s.getLeadSlot());
399                        }
400                        t = Bundle.getMessage("SlotConsistSubX", s.speed());
401                        return t;
402                    case LnConstants.CONSIST_NO:
403                        return Bundle.getMessage("SlotConsistNone");
404                    default:
405                        return Bundle.getMessage("StateError");
406                }
407            case CONSISTADDRESS:
408                if (s.isSystemSlot()) {
409                    // makes no sense to decode flag to rendered NA
410                    return -1;
411                }
412                switch (s.consistStatus()) {
413                    case LnConstants.CONSIST_TOP:
414                        return s.locoAddr();
415                    case LnConstants.CONSIST_MID:
416                    case LnConstants.CONSIST_SUB:
417                        if (s.getProtocol() == LnConstants.LOCONETPROTOCOL_TWO) {
418                            return memo.getSlotManager().slot(s.getLeadSlot()).locoAddr();
419                        }
420                        return memo.getSlotManager().slot(s.speed()).locoAddr();
421                    case LnConstants.CONSIST_NO:
422                        return 0;
423                    default:
424                        return 0;
425                }
426            case DISPCOLUMN:
427                return Bundle.getMessage("ButtonRelease"); // will be name of button in default GUI
428            case DIRCOLUMN:
429                return s.isForward() ? Bundle.getMessage("DirColForward") : Bundle.getMessage("DirColReverse");
430            case F0COLUMN:
431                return s.isF0();
432            case F1COLUMN:
433                return s.isF1();
434            case F2COLUMN:
435                return s.isF2();
436            case F3COLUMN:
437                return s.isF3();
438            case F4COLUMN:
439                return s.isF4();
440            case F5COLUMN:
441                return s.isF5();
442            case F6COLUMN:
443                return s.isF6();
444            case F7COLUMN:
445                return s.isF7();
446            case F8COLUMN:
447                return s.isF8();
448            case F9COLUMN:
449                return s.isF9();
450            case F10COLUMN:
451                return s.isF10();
452            case F11COLUMN:
453                return s.isF11();
454            case F12COLUMN:
455                return s.isF12();
456            case F13COLUMN:
457                return s.isF13();
458            case F14COLUMN:
459                return s.isF14();
460            case F15COLUMN:
461                return s.isF15();
462            case F16COLUMN:
463                return s.isF16();
464            case F17COLUMN:
465                return s.isF17();
466            case F18COLUMN:
467                return s.isF18();
468            case F19COLUMN:
469                return s.isF19();
470            case F20COLUMN:
471                return s.isF20();
472            case F21COLUMN:
473                return s.isF21();
474            case F22COLUMN:
475                return s.isF22();
476            case F23COLUMN:
477                return s.isF23();
478            case F24COLUMN:
479                return s.isF24();
480            case F25COLUMN:
481                return s.isF25();
482            case F26COLUMN:
483                return s.isF26();
484            case F27COLUMN:
485                return s.isF27();
486            case F28COLUMN:
487                return s.isF28();
488            case THROTCOLUMN:
489                int upper = (s.id() >> 7) & 0x7F;
490                int lower = s.id() & 0x7F;
491                return StringUtil.twoHexFromInt(upper) + " " + StringUtil.twoHexFromInt(lower);
492            case JMRITHROTTLECOUNT:
493                return(memo.getThrottleManager().getThrottleUsageCount(s.locoAddr()));
494            case LASTUPDATE:
495                Instant instant = Instant.ofEpochMilli(s.getLastUpdateTime());
496                ZonedDateTime dateTime = instant.atZone(ZoneId.systemDefault());
497                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
498                String formattedDateTime = dateTime.format(formatter);
499                if (s.getSlowScanStartedAt() != 0) {
500                    formattedDateTime=Bundle.getMessage("LastUpdateSlowScan",formattedDateTime);
501                }
502                return formattedDateTime;
503            default:
504                log.error("internal state inconsistent with table requst for {} {}", row, col);
505                return null;
506        }
507    }
508
509    public int getPreferredWidth(int col) {
510        ColumnNumber colorder = ColumnNumber.values()[col];
511        switch (colorder) {
512            case SLOTCOLUMN:
513            case JMRITHROTTLECOUNT:
514                return new JTextField(3).getPreferredSize().width;
515            case ESTOPCOLUMN:
516                return new JButton(Bundle.getMessage("ButtonEstop")).getPreferredSize().width;
517            case ADDRCOLUMN:
518                return new JTextField(5).getPreferredSize().width;
519            case SPDCOLUMN:
520            case STATCOLUMN:
521                return new JTextField(6).getPreferredSize().width;
522            case TYPECOLUMN:
523                return new JTextField(12).getPreferredSize().width;
524            case CONSCOLUMN:
525                return new JTextField(4).getPreferredSize().width;
526            case CONSISTADDRESS:
527                return new JTextField(" 0000 ").getPreferredSize().width;
528            case DIRCOLUMN:
529                // the length of an empty JTextField works on more GUIs
530                int m = Math.max(Bundle.getMessage("DirColForward").length(), Bundle.getMessage("DirColReverse").length());
531                m = Math.max(m, Bundle.getMessage("DirectionCol").length());
532                return new JTextField(m).getPreferredSize().width;
533            case DISPCOLUMN:
534                return new JButton(Bundle.getMessage("ButtonRelease")).getPreferredSize().width;
535            case THROTCOLUMN:
536                return new JTextField(7).getPreferredSize().width;
537            case LASTUPDATE:
538                return new JTextField("SS HH:MM:SS ").getPreferredSize().width;
539            case F0COLUMN:
540            case F1COLUMN:
541            case F2COLUMN:
542            case F3COLUMN:
543            case F4COLUMN:
544            case F5COLUMN:
545            case F6COLUMN:
546            case F7COLUMN:
547            case F8COLUMN:
548            case F9COLUMN:
549            case F10COLUMN:
550            case F11COLUMN:
551            case F12COLUMN:
552            case F13COLUMN:
553            case F14COLUMN:
554            case F15COLUMN:
555            case F16COLUMN:
556            case F17COLUMN:
557            case F18COLUMN:
558            case F19COLUMN:
559            case F20COLUMN:
560            case F21COLUMN:
561            case F22COLUMN:
562            case F23COLUMN:
563            case F24COLUMN:
564            case F25COLUMN:
565            case F26COLUMN:
566            case F27COLUMN:
567            case F28COLUMN:
568                // to show checkboxes and Text
569                return Math.max(new JCheckBox().getPreferredSize().width, new JTextField("F99").getPreferredSize().width);
570            default:
571                return new JLabel(" <unknown> ").getPreferredSize().width; // NOI18N
572        }
573    }
574
575    @Override
576    public void setValueAt(Object value, int row, int col) {
577        LocoNetMessage msg;
578        LocoNetSlot s = memo.getSlotManager().slot(row);
579        if (s == null) {
580            log.error("slot pointer was null for slot row: {} col: {}", row, col);
581            return;
582        }
583
584        ColumnNumber colorder = ColumnNumber.values()[col];
585        switch (colorder) {
586            case ESTOPCOLUMN:
587                log.debug("Start estop in slot {}", row);
588                if ((s.consistStatus() == LnConstants.CONSIST_SUB)
589                        || (s.consistStatus() == LnConstants.CONSIST_MID)) {
590                    Object[] options = {Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonCancel")};
591                    int result = JmriJOptionPane.showOptionDialog(null,
592                                    Bundle.getMessage("SlotEstopWarning"),
593                                    Bundle.getMessage("WarningTitle"),
594                                    JmriJOptionPane.DEFAULT_OPTION,
595                                    JmriJOptionPane.WARNING_MESSAGE,
596                                    null, options, options[1]);
597                    if ( result != 0 ) { // not array position 0 ButtonOK
598                        return;
599                    }
600                }
601                msg = s.writeSpeed(1);
602                memo.getLnTrafficController().sendLocoNetMessage(msg);
603                fireTableRowsUpdated(row, row);
604                break;
605            case F0COLUMN:
606                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
607                    sendFunctionGroup1(s, col, row);
608                } else {
609                    sendExpFunctionGroup1(s, col, row);
610                }
611                fireTableRowsUpdated(row, row);
612                break;
613            case F1COLUMN:
614                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
615                    sendFunctionGroup1(s, col, row);
616                } else {
617                    sendExpFunctionGroup1(s, col, row);
618                }
619                fireTableRowsUpdated(row, row);
620                break;
621            case F2COLUMN:
622                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
623                    sendFunctionGroup1(s, col, row);
624                } else {
625                    sendExpFunctionGroup1(s, col, row);
626                }
627                fireTableRowsUpdated(row, row);
628                break;
629            case F3COLUMN:
630                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
631                    sendFunctionGroup1(s, col, row);
632                } else {
633                    sendExpFunctionGroup1(s, col, row);
634                }
635                fireTableRowsUpdated(row, row);
636                break;
637            case F4COLUMN:
638                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
639                    sendFunctionGroup1(s, col, row);
640                } else {
641                    sendExpFunctionGroup1(s, col, row);
642                }
643                fireTableRowsUpdated(row, row);
644                break;
645            case F5COLUMN:
646                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
647                    sendFunctionGroup2(s, col, row);
648                } else {
649                    sendExpFunctionGroup1(s, col, row);
650                }
651                fireTableRowsUpdated(row, row);
652                break;
653            case F6COLUMN:
654                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
655                    sendFunctionGroup2(s, col, row);
656                } else {
657                    sendExpFunctionGroup1(s, col, row);
658                }
659                fireTableRowsUpdated(row, row);
660                break;
661            case F7COLUMN:
662                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
663                    sendFunctionGroup2(s, col, row);
664                } else {
665                    sendExpFunctionGroup2(s, col, row);
666                }
667                fireTableRowsUpdated(row, row);
668                break;
669            case F8COLUMN:
670                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
671                    sendFunctionGroup2(s, col, row);
672                } else {
673                    sendExpFunctionGroup2(s, col, row);
674                }
675                fireTableRowsUpdated(row, row);
676                break;
677            case F9COLUMN:
678                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
679                    sendFunctionGroup3(s, col, row);
680                } else {
681                    sendExpFunctionGroup2(s, col, row);
682                }
683                fireTableRowsUpdated(row, row);
684                break;
685            case F10COLUMN:
686                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
687                    sendFunctionGroup3(s, col, row);
688                } else {
689                    sendExpFunctionGroup2(s, col, row);
690                }
691                fireTableRowsUpdated(row, row);
692                break;
693            case F11COLUMN:
694                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
695                    sendFunctionGroup3(s, col, row);
696                } else {
697                    sendExpFunctionGroup2(s, col, row);
698                }
699                fireTableRowsUpdated(row, row);
700                break;
701            case F12COLUMN:
702                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
703                    sendFunctionGroup3(s, col, row);
704                } else {
705                    sendExpFunctionGroup2(s, col, row);
706                }
707                fireTableRowsUpdated(row, row);
708                break;
709            case F13COLUMN:
710                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
711                    sendFunctionGroup4(s, col, row);
712                } else {
713                    sendExpFunctionGroup2(s, col, row);
714                }
715                fireTableRowsUpdated(row, row);
716                break;
717            case F14COLUMN:
718                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
719                    sendFunctionGroup4(s, col, row);
720                } else {
721                    sendExpFunctionGroup3(s, col, row);
722                }
723                fireTableRowsUpdated(row, row);
724                break;
725            case F15COLUMN:
726                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
727                    sendFunctionGroup4(s, col, row);
728                } else {
729                    sendExpFunctionGroup3(s, col, row);
730                }
731                fireTableRowsUpdated(row, row);
732                break;
733            case F16COLUMN:
734                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
735                    sendFunctionGroup4(s, col, row);
736                } else {
737                    sendExpFunctionGroup3(s, col, row);
738                }
739                fireTableRowsUpdated(row, row);
740                break;
741            case F17COLUMN:
742                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
743                    sendFunctionGroup4(s, col, row);
744                } else {
745                    sendExpFunctionGroup3(s, col, row);
746                }
747                fireTableRowsUpdated(row, row);
748                break;
749            case F18COLUMN:
750                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
751                    sendFunctionGroup4(s, col, row);
752                } else {
753                    sendExpFunctionGroup3(s, col, row);
754                }
755                fireTableRowsUpdated(row, row);
756                break;
757            case F19COLUMN:
758                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
759                    sendFunctionGroup4(s, col, row);
760                } else {
761                    sendExpFunctionGroup3(s, col, row);
762                }
763                fireTableRowsUpdated(row, row);
764                break;
765            case F20COLUMN:
766                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
767                    sendFunctionGroup4(s, col, row);
768                } else {
769                    sendExpFunctionGroup3(s, col, row);
770                }
771                fireTableRowsUpdated(row, row);
772                break;
773            case F21COLUMN:
774                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
775                    sendFunctionGroup5(s, col, row);
776                } else {
777                    sendExpFunctionGroup4(s, col, row);
778                }
779                fireTableRowsUpdated(row, row);
780                break;
781            case F22COLUMN:
782                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
783                    sendFunctionGroup5(s, col, row);
784                } else {
785                    sendExpFunctionGroup4(s, col, row);
786                }
787                fireTableRowsUpdated(row, row);
788                break;
789            case F23COLUMN:
790                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
791                    sendFunctionGroup5(s, col, row);
792                } else {
793                    sendExpFunctionGroup4(s, col, row);
794                }
795                fireTableRowsUpdated(row, row);
796                break;
797            case F24COLUMN:
798                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
799                    sendFunctionGroup5(s, col, row);
800                } else {
801                    sendExpFunctionGroup4(s, col, row);
802                }
803                fireTableRowsUpdated(row, row);
804                break;
805            case F25COLUMN:
806                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
807                    sendFunctionGroup5(s, col, row);
808                } else {
809                    sendExpFunctionGroup4(s, col, row);
810                }
811                fireTableRowsUpdated(row, row);
812                break;
813            case F26COLUMN:
814                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
815                    sendFunctionGroup5(s, col, row);
816                } else {
817                    sendExpFunctionGroup4(s, col, row);
818                }
819                fireTableRowsUpdated(row, row);
820                break;
821            case F27COLUMN:
822                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
823                    sendFunctionGroup5(s, col, row);
824                } else {
825                    sendExpFunctionGroup4(s, col, row);
826                }
827                fireTableRowsUpdated(row, row);
828                break;
829            case F28COLUMN:
830                if (s.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) {
831                    sendFunctionGroup5(s, col, row);
832                } else {
833                    sendExpFunctionGroup4(s, col, row);
834                }
835                fireTableRowsUpdated(row, row);
836                break;
837            case DISPCOLUMN:
838                log.debug("Start freeing slot {}", row);
839                if (s.slotStatus() != LnConstants.LOCO_FREE) {
840                    if (s.consistStatus() != LnConstants.CONSIST_NO) {
841                        // Freeing a member takes it out of the consist
842                        // entirely (i.e., while the slot is LOCO_FREE, it
843                        // still reads the former consist information, but
844                        // the next time that loco is selected, it comes
845                        // back as CONSIST_NO).  Freeing the CONSIST_TOP
846                        // will kill the entire consist.
847                        Object[] options = {Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonCancel")};
848                        int result = JmriJOptionPane.showOptionDialog(null,
849                                        "Freeing a consist member will destroy the consist.\n\nAre you sure you want to do that?",
850                                        Bundle.getMessage("WarningTitle"),
851                                        JmriJOptionPane.DEFAULT_OPTION,
852                                        JmriJOptionPane.WARNING_MESSAGE,
853                                        null, options, options[1]);
854                        if ( result != 0 ) { // not array position 0, ButtonOK
855                            return;
856                        }
857                    }
858                    // send status to free
859                    memo.getLnTrafficController().sendLocoNetMessage(
860                            s.writeStatus(LnConstants.LOCO_FREE));
861                } else {
862                    log.debug("Slot not in use");
863                }
864                fireTableRowsUpdated(row, row);
865                break;
866            default:
867                // nothing to do if column not recognized
868                break;
869        }
870    }
871
872    //Added by Jeffrey Machacek, date: 2013
873    //changed 8/22/2013
874    public void clearAllSlots() {
875        int count = getRowCount();
876
877        for (int row = 0; row < (count - 1); row++) {
878            LocoNetSlot s = memo.getSlotManager().slot(row);
879
880            if (s.getSlotType() == SlotType.LOCO
881                    && ((s.slotStatus() != LnConstants.LOCO_IN_USE) && (s.consistStatus() == LnConstants.CONSIST_NO))) {
882                log.debug("Freeing {} from slot {}, old status: {}", s.locoAddr(), s.getSlot(), s.slotStatus());
883                memo.getLnTrafficController().sendLocoNetMessage(
884                        s.writeStatus(LnConstants.LOCO_FREE
885                        ));
886                fireTableRowsUpdated(row, row);
887            }
888            count = getRowCount();
889        }
890    }
891
892    /**
893     * Configure a table to have our standard rows and columns. This is
894     * optional, in that other table formats can use this table model. But we
895     * put it here to help keep it consistent.
896     *
897     * @param slotTable the table to configure
898     */
899    public void configureTable(JTable slotTable) {
900    }
901
902    // methods to communicate with SlotManager
903    @Override
904    public synchronized void notifyChangedSlot(LocoNetSlot s) {
905        // update model from this slot
906        int slotNum = s.getSlot();
907        int slotStatus2;
908
909        if (slotNum == LnConstants.CFG_SLOT) {
910            slotStatus2 = s.ss2() & 0x78; // Bit 3-6 of SS2 contains SW36-39 of the CFG_SLOT
911            if (slotStatus2 > 0) {
912                memo.getSlotManager().update();
913            }
914        } else {
915            slotNum = -1; // all rows
916        }
917
918        // notify the JTable object that a row has changed; do that in the Swing thread!
919        javax.swing.SwingUtilities.invokeLater(new Notify(slotNum, this));
920    }
921
922    private static class Notify implements Runnable {
923
924        private final int _row;
925        javax.swing.table.AbstractTableModel _model;
926
927        public Notify(int row, javax.swing.table.AbstractTableModel model) {
928            _row = row;
929            _model = model;
930        }
931
932        @Override
933        public void run() {
934            if (-1 == _row) {  // notify about entire table
935                _model.fireTableDataChanged();  // just that row
936            } else {
937                // notify that _row has changed
938                _model.fireTableRowsUpdated(_row, _row);  // just that row
939            }
940        }
941    }
942
943    protected LocoNetSlot getSlot(int row) {
944        return memo.getSlotManager().slot(row);
945    }
946
947    /**
948     * This is a convenience method that makes it easier for classes using this
949     * model to set all in-use slots to emergency stop
950     */
951    public void estopAll() {
952        for (int slotNum = 0; slotNum < 120; slotNum++) {
953            LocoNetSlot s = memo.getSlotManager().slot(slotNum);
954            if (s.slotStatus() != LnConstants.LOCO_FREE
955                    && (s.consistStatus() == LnConstants.CONSIST_NO
956                    || s.consistStatus() == LnConstants.CONSIST_TOP)
957                    && s.speed() != 1) {
958                // send message to estop this loco
959                LocoNetMessage msg = s.writeSpeed(1) ; // emergency stop
960                memo.getLnTrafficController().sendLocoNetMessage(msg);
961            }
962        }
963    }
964
965    /**
966     * Send the LocoNet message to set the state of locomotive direction and
967     * functions F0, F1, F2, F3, F4
968     * @param slot loconet slot
969     * @param col  grid col
970     * @param row  grid row
971     */
972    protected void sendFunctionGroup1(LocoNetSlot slot, int col, int row) {
973        log.debug("F0-F4 change requested {}", row);
974        if (slot == null) {
975            log.error("slot pointer was null for slot row: {} col: {}", row, col);
976            return;
977        }
978        boolean tempF0 = (col == ColumnNumber.F0COLUMN.ordinal()) ? !slot.isF0() : slot.isF0();
979        boolean tempF1 = (col == ColumnNumber.F1COLUMN.ordinal()) ? !slot.isF1() : slot.isF1();
980        boolean tempF2 = (col == ColumnNumber.F2COLUMN.ordinal()) ? !slot.isF2() : slot.isF2();
981        boolean tempF3 = (col == ColumnNumber.F3COLUMN.ordinal()) ? !slot.isF3() : slot.isF3();
982        boolean tempF4 = (col == ColumnNumber.F4COLUMN.ordinal()) ? !slot.isF4() : slot.isF4();
983
984        int new_dirf = ((slot.isForward() ? 0 : LnConstants.DIRF_DIR)
985                | (tempF0 ? LnConstants.DIRF_F0 : 0)
986                | (tempF1 ? LnConstants.DIRF_F1 : 0)
987                | (tempF2 ? LnConstants.DIRF_F2 : 0)
988                | (tempF3 ? LnConstants.DIRF_F3 : 0)
989                | (tempF4 ? LnConstants.DIRF_F4 : 0));
990
991        // set status to 'In Use' if other
992        int status = slot.slotStatus();
993        if (status != LnConstants.LOCO_IN_USE) {
994            memo.getLnTrafficController().sendLocoNetMessage(
995                    slot.writeStatus(LnConstants.LOCO_IN_USE
996                    ));
997        }
998        LocoNetMessage msg = new LocoNetMessage(4);
999        msg.setOpCode(LnConstants.OPC_LOCO_DIRF);
1000        msg.setElement(1, slot.getSlot());
1001        msg.setElement(2, new_dirf);       // 1 here is estop
1002        memo.getLnTrafficController().sendLocoNetMessage(msg, true);
1003        // Delay here allows command station time to xmit on the rails.
1004        try {
1005            Thread.sleep(100);
1006        } catch (InterruptedException ex) {
1007            log.error("Interuppted Sleep", ex);
1008        }
1009        // reset status to original value if not previously 'in use'
1010        if (status != LnConstants.LOCO_IN_USE) {
1011            memo.getLnTrafficController().sendLocoNetMessage(
1012                    slot.writeStatus(status));
1013        }
1014    }
1015
1016    /**
1017     * Send the LocoNet message to set the state of functions F5, F6, F7, F8
1018     * @param slot loconet slot
1019     * @param col  grid col
1020     * @param row  grid row
1021     */
1022    protected void sendFunctionGroup2(LocoNetSlot slot, int col, int row) {
1023        boolean tempF5 = (col == ColumnNumber.F5COLUMN.ordinal()) ? !slot.isF5() : slot.isF5();
1024        boolean tempF6 = (col == ColumnNumber.F6COLUMN.ordinal()) ? !slot.isF6() : slot.isF6();
1025        boolean tempF7 = (col == ColumnNumber.F7COLUMN.ordinal()) ? !slot.isF7() : slot.isF7();
1026        boolean tempF8 = (col == ColumnNumber.F8COLUMN.ordinal()) ? !slot.isF8() : slot.isF8();
1027
1028        int new_snd = ((tempF8 ? LnConstants.SND_F8 : 0)
1029                | (tempF7 ? LnConstants.SND_F7 : 0)
1030                | (tempF6 ? LnConstants.SND_F6 : 0)
1031                | (tempF5 ? LnConstants.SND_F5 : 0));
1032
1033        // set status to 'In Use' if other
1034        int status = slot.slotStatus();
1035        if (status != LnConstants.LOCO_IN_USE) {
1036            memo.getLnTrafficController().sendLocoNetMessage(
1037                    slot.writeStatus(LnConstants.LOCO_IN_USE
1038                    ));
1039        }
1040
1041        LocoNetMessage msg = new LocoNetMessage(4);
1042        msg.setOpCode(LnConstants.OPC_LOCO_SND);
1043        msg.setElement(1, slot.getSlot());
1044        msg.setElement(2, new_snd);       // 1 here is estop
1045        memo.getLnTrafficController().sendLocoNetMessage(msg, true);
1046        // Delay here allows command station time to xmit on the rails.
1047        try {
1048            Thread.sleep(100);
1049        } catch (InterruptedException ex) {
1050            log.error("Interuppted Sleep", ex);
1051        }
1052
1053        // reset status to original value if not previously 'in use'
1054        if (status != LnConstants.LOCO_IN_USE) {
1055            memo.getLnTrafficController().sendLocoNetMessage(
1056                    slot.writeStatus(status));
1057        }
1058    }
1059
1060    /**
1061     * Sends Function Group 3 values - F9 thru F12, using an "OPC_IMM_PACKET" LocoNet
1062     * Message.
1063     * @param slot loconet slot
1064     * @param col  grid col
1065     * @param row  grid row
1066     */
1067     protected void sendFunctionGroup3(LocoNetSlot slot, int col, int row) {
1068        // LocoNet practice is to send F9-F12 as a DCC packet
1069        boolean tempF9 = (col == ColumnNumber.F9COLUMN.ordinal()) ? !slot.isF9() : slot.isF9();
1070        boolean tempF10 = (col == ColumnNumber.F10COLUMN.ordinal()) ? !slot.isF10() : slot.isF10();
1071        boolean tempF11 = (col == ColumnNumber.F11COLUMN.ordinal()) ? !slot.isF11() : slot.isF11();
1072        boolean tempF12 = (col == ColumnNumber.F12COLUMN.ordinal()) ? !slot.isF12() : slot.isF12();
1073        byte[] result = jmri.NmraPacket.function9Through12Packet(slot.locoAddr(), (slot.locoAddr() >= 128),
1074                tempF9, tempF10, tempF11, tempF12);
1075
1076        log.debug("sendFunctionGroup3 sending {} to LocoNet slot {}", result, slot.getSlot());
1077        memo.get(jmri.CommandStation.class).sendPacket(result, 4); // repeat = 4
1078    }
1079
1080    /**
1081     * Sends Function Group 4 values - F13 thru F20, using an "OPC_IMM_PACKET" LocoNet
1082     * Message.
1083     * @param slot loconet slot
1084     * @param col  grid col
1085     * @param row  grid row
1086     */
1087    protected void sendFunctionGroup4(LocoNetSlot slot, int col, int row) {
1088        // LocoNet practice is to send F13-F20 as a DCC packet
1089        boolean tempF13 = (col == ColumnNumber.F13COLUMN.ordinal()) ? !slot.isF13() : slot.isF13();
1090        boolean tempF14 = (col == ColumnNumber.F14COLUMN.ordinal()) ? !slot.isF14() : slot.isF14();
1091        boolean tempF15 = (col == ColumnNumber.F15COLUMN.ordinal()) ? !slot.isF15() : slot.isF15();
1092        boolean tempF16 = (col == ColumnNumber.F16COLUMN.ordinal()) ? !slot.isF16() : slot.isF16();
1093        boolean tempF17 = (col == ColumnNumber.F17COLUMN.ordinal()) ? !slot.isF17() : slot.isF17();
1094        boolean tempF18 = (col == ColumnNumber.F18COLUMN.ordinal()) ? !slot.isF18() : slot.isF18();
1095        boolean tempF19 = (col == ColumnNumber.F19COLUMN.ordinal()) ? !slot.isF19() : slot.isF19();
1096        boolean tempF20 = (col == ColumnNumber.F20COLUMN.ordinal()) ? !slot.isF20() : slot.isF20();
1097        byte[] result = jmri.NmraPacket.function13Through20Packet(slot.locoAddr(), (slot.locoAddr() >= 128),
1098                tempF13, tempF14, tempF15, tempF16,
1099                tempF17, tempF18, tempF19, tempF20);
1100
1101        log.debug("sendFunctionGroup4 sending {} to LocoNet slot {}", result, slot.getSlot());
1102        memo.get(jmri.CommandStation.class).sendPacket(result, 4); // repeat = 4
1103    }
1104
1105    /**
1106     * Sends Function Group 5 values - F21 thru F28, using an "OPC_IMM_PACKET" LocoNet
1107     * Message.
1108     * @param slot loconet slot
1109     * @param col  grid col
1110     * @param row  grid row
1111     */
1112    protected void sendFunctionGroup5(LocoNetSlot slot, int col, int row) {
1113        // LocoNet practice is to send F21-F28 as a DCC packet
1114        // LocoNet practice is to send F13-F20 as a DCC packet
1115        boolean tempF21 = (col == ColumnNumber.F21COLUMN.ordinal()) ? !slot.isF21() : slot.isF21();
1116        boolean tempF22 = (col == ColumnNumber.F22COLUMN.ordinal()) ? !slot.isF22() : slot.isF22();
1117        boolean tempF23 = (col == ColumnNumber.F23COLUMN.ordinal()) ? !slot.isF23() : slot.isF23();
1118        boolean tempF24 = (col == ColumnNumber.F24COLUMN.ordinal()) ? !slot.isF24() : slot.isF24();
1119        boolean tempF25 = (col == ColumnNumber.F25COLUMN.ordinal()) ? !slot.isF25() : slot.isF25();
1120        boolean tempF26 = (col == ColumnNumber.F26COLUMN.ordinal()) ? !slot.isF26() : slot.isF26();
1121        boolean tempF27 = (col == ColumnNumber.F27COLUMN.ordinal()) ? !slot.isF27() : slot.isF27();
1122        boolean tempF28 = (col == ColumnNumber.F28COLUMN.ordinal()) ? !slot.isF28() : slot.isF28();
1123        byte[] result = jmri.NmraPacket.function21Through28Packet(slot.locoAddr(), (slot.locoAddr() >= 128),
1124                tempF21, tempF22, tempF23, tempF24,
1125                tempF25, tempF26, tempF27, tempF28);
1126
1127        log.debug("sendFunctionGroup5 sending {} to LocoNet slot {}", result, slot.getSlot());
1128        memo.get(jmri.CommandStation.class).sendPacket(result, 4); // repeat = 4
1129    }
1130
1131    /**
1132     * Send the Expanded LocoNet message to set the state of locomotive direction and
1133     * functions F0, F1, F2, F3, F4, F5, F6
1134     * @param slot loconet slot
1135     * @param col  grid col
1136     * @param row  grid row
1137     */
1138    protected void sendExpFunctionGroup1(LocoNetSlot slot, int col, int row) {
1139        boolean tempF0 = (col == ColumnNumber.F0COLUMN.ordinal()) ? !slot.isF0() : slot.isF0();
1140        boolean tempF1 = (col == ColumnNumber.F1COLUMN.ordinal()) ? !slot.isF1() : slot.isF1();
1141        boolean tempF2 = (col == ColumnNumber.F2COLUMN.ordinal()) ? !slot.isF2() : slot.isF2();
1142        boolean tempF3 = (col == ColumnNumber.F3COLUMN.ordinal()) ? !slot.isF3() : slot.isF3();
1143        boolean tempF4 = (col == ColumnNumber.F4COLUMN.ordinal()) ? !slot.isF4() : slot.isF4();
1144        boolean tempF5 = (col == ColumnNumber.F5COLUMN.ordinal()) ? !slot.isF5() : slot.isF5();
1145        boolean tempF6 = (col == ColumnNumber.F6COLUMN.ordinal()) ? !slot.isF6() : slot.isF6();
1146        int new_F0F6 = ((tempF5 ? 0b00100000 : 0) | (tempF6 ? 0b01000000 : 0)
1147                | (tempF0 ? LnConstants.DIRF_F0 : 0)
1148                | (tempF1 ? LnConstants.DIRF_F1 : 0)
1149                | (tempF2 ? LnConstants.DIRF_F2 : 0)
1150                | (tempF3 ? LnConstants.DIRF_F3 : 0)
1151                | (tempF4 ? LnConstants.DIRF_F4 : 0));
1152            LocoNetMessage msg = new LocoNetMessage(6);
1153            msg.setOpCode(0xd5);
1154            msg.setElement(1, (slot.getSlot() / 128) | 0b00010000 );
1155            msg.setElement(2,slot.getSlot() & 0b01111111);
1156            msg.setElement(3,slot.id() & 0x7F);
1157            msg.setElement(4, new_F0F6);
1158            memo.getLnTrafficController().sendLocoNetMessage(msg, true);
1159    }
1160
1161    /**
1162     * Send the Expanded LocoNet message to set the state of functions F7, F8, F8, F9, F10, F11, F12, F13
1163     * @param slot loconet slot
1164     * @param col  grid col
1165     * @param row  grid row
1166     */
1167    protected void sendExpFunctionGroup2(LocoNetSlot slot, int col, int row) {
1168        boolean tempF7 = (col == ColumnNumber.F7COLUMN.ordinal()) ? !slot.isF7() : slot.isF7();
1169        boolean tempF8 = (col == ColumnNumber.F8COLUMN.ordinal()) ? !slot.isF8() : slot.isF8();
1170        boolean tempF9 = (col == ColumnNumber.F9COLUMN.ordinal()) ? !slot.isF9() : slot.isF9();
1171        boolean tempF10 = (col == ColumnNumber.F10COLUMN.ordinal()) ? !slot.isF10() : slot.isF10();
1172        boolean tempF11 = (col == ColumnNumber.F11COLUMN.ordinal()) ? !slot.isF11() : slot.isF11();
1173        boolean tempF12 = (col == ColumnNumber.F12COLUMN.ordinal()) ? !slot.isF12() : slot.isF12();
1174        boolean tempF13 = (col == ColumnNumber.F13COLUMN.ordinal()) ? !slot.isF13() : slot.isF13();
1175            int new_F7F13 = ((tempF7 ? 0b00000001 : 0) | (tempF8 ? 0b00000010 : 0)
1176                    | (tempF9  ? 0b00000100 : 0)
1177                    | (tempF10 ? 0b00001000 : 0)
1178                    | (tempF11 ? 0b00010000 : 0)
1179                    | (tempF12 ? 0b00100000 : 0)
1180                    | (tempF13 ? 0b01000000 : 0));
1181                LocoNetMessage msg = new LocoNetMessage(6);
1182                msg.setOpCode(0xd5);
1183                msg.setElement(1, (slot.getSlot() / 128) | 0b00011000 );
1184                msg.setElement(2,slot.getSlot() & 0b01111111);
1185                msg.setElement(3,slot.id() & 0x7F);
1186                msg.setElement(4, new_F7F13);
1187                memo.getLnTrafficController().sendLocoNetMessage(msg, true);
1188    }
1189
1190    /**
1191     * Sends expanded loconet message F14 thru F20
1192     * Message.
1193     * @param slot loconet slot
1194     * @param col  grid col
1195     * @param row  grid row
1196     */
1197    protected void sendExpFunctionGroup3(LocoNetSlot slot, int col, int row) {
1198        boolean tempF14 = (col == ColumnNumber.F14COLUMN.ordinal()) ? !slot.isF14() : slot.isF14();
1199        boolean tempF15 = (col == ColumnNumber.F15COLUMN.ordinal()) ? !slot.isF15() : slot.isF15();
1200        boolean tempF16 = (col == ColumnNumber.F16COLUMN.ordinal()) ? !slot.isF16() : slot.isF16();
1201        boolean tempF17 = (col == ColumnNumber.F17COLUMN.ordinal()) ? !slot.isF17() : slot.isF17();
1202        boolean tempF18 = (col == ColumnNumber.F18COLUMN.ordinal()) ? !slot.isF18() : slot.isF18();
1203        boolean tempF19 = (col == ColumnNumber.F19COLUMN.ordinal()) ? !slot.isF19() : slot.isF19();
1204        boolean tempF20 = (col == ColumnNumber.F20COLUMN.ordinal()) ? !slot.isF20() : slot.isF20();
1205        int new_F14F20 = ((tempF14 ? 0b00000001 : 0) | (tempF15 ? 0b00000010 : 0)
1206                | (tempF16  ? 0b00000100 : 0)
1207                | (tempF17 ? 0b00001000 : 0)
1208                | (tempF18 ? 0b00010000 : 0)
1209                | (tempF19 ? 0b00100000 : 0)
1210                | (tempF20 ? 0b01000000 : 0));
1211            LocoNetMessage msg = new LocoNetMessage(6);
1212            msg.setOpCode(0xd5);
1213            msg.setElement(1, (slot.getSlot() / 128) | 0b00100000 );
1214            msg.setElement(2,slot.getSlot() & 0b01111111);
1215            msg.setElement(3,slot.id() & 0x7F);
1216            msg.setElement(4, new_F14F20);
1217            memo.getLnTrafficController().sendLocoNetMessage(msg, true);
1218    }
1219
1220    /**
1221     * Sends Expanded loconet message F21 thru F28 Message.
1222     * @param slot loconet slot
1223     * @param col  grid col
1224     * @param row  grid row
1225     */
1226    protected void sendExpFunctionGroup4(LocoNetSlot slot, int col, int row) {
1227        boolean tempF21 = (col == ColumnNumber.F21COLUMN.ordinal()) ? !slot.isF21() : slot.isF21();
1228        boolean tempF22 = (col == ColumnNumber.F22COLUMN.ordinal()) ? !slot.isF22() : slot.isF22();
1229        boolean tempF23 = (col == ColumnNumber.F23COLUMN.ordinal()) ? !slot.isF23() : slot.isF23();
1230        boolean tempF24 = (col == ColumnNumber.F24COLUMN.ordinal()) ? !slot.isF24() : slot.isF24();
1231        boolean tempF25 = (col == ColumnNumber.F25COLUMN.ordinal()) ? !slot.isF25() : slot.isF25();
1232        boolean tempF26 = (col == ColumnNumber.F26COLUMN.ordinal()) ? !slot.isF26() : slot.isF26();
1233        boolean tempF27 = (col == ColumnNumber.F27COLUMN.ordinal()) ? !slot.isF27() : slot.isF27();
1234        boolean tempF28 = (col == ColumnNumber.F28COLUMN.ordinal()) ? !slot.isF28() : slot.isF28();
1235        int new_F14F20 = ((tempF21 ? 0b00000001 : 0) |
1236                (tempF22 ? 0b00000010 : 0) |
1237                (tempF23 ? 0b00000100 : 0) |
1238                (tempF24 ? 0b00001000 : 0) |
1239                (tempF25 ? 0b00010000 : 0) |
1240                (tempF26 ? 0b00100000 : 0) |
1241                (tempF27 ? 0b01000000 : 0));
1242        LocoNetMessage msg = new LocoNetMessage(6);
1243        msg.setOpCode(0xd5);
1244        if (!tempF28) {
1245            msg.setElement(1, (slot.getSlot() / 128) | LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28OFF);
1246        } else {
1247            msg.setElement(1, (slot.getSlot() / 128) | LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28ON);
1248        }
1249        msg.setElement(2, slot.getSlot() &  0x7F);
1250        msg.setElement(3, slot.id() & 0x7F);
1251        msg.setElement(4, new_F14F20);
1252        memo.getLnTrafficController().sendLocoNetMessage(msg, true);
1253    }
1254
1255    // gets called on SlotMonPane.dispose
1256    public void dispose() {
1257        memo.getSlotManager().removeSlotListener(this);
1258    }
1259
1260    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SlotMonDataModel.class);
1261
1262}