View Javadoc
1   /*******************************************************************************
2    * jArduino: Arduino C++ Code Generation From Java
3    * Copyright 2020 Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   ******************************************************************************/
17  package net.sourceforge.jarduino.gui;
18  
19  import java.awt.Color;
20  import java.awt.Component;
21  import java.awt.event.FocusEvent;
22  import java.awt.event.FocusListener;
23  import java.text.NumberFormat;
24  import java.text.ParseException;
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  import javax.swing.AbstractCellEditor;
30  import javax.swing.JComponent;
31  import javax.swing.JTable;
32  import javax.swing.JTextField;
33  import javax.swing.RowFilter;
34  import javax.swing.SwingConstants;
35  import javax.swing.border.Border;
36  import javax.swing.border.LineBorder;
37  import javax.swing.table.AbstractTableModel;
38  import javax.swing.table.DefaultTableCellRenderer;
39  import javax.swing.table.TableCellEditor;
40  import javax.swing.table.TableCellRenderer;
41  import javax.swing.table.TableColumn;
42  import javax.swing.table.TableColumnModel;
43  import javax.swing.table.TableRowSorter;
44  
45  import net.sourceforge.jarduino.message.ArduinoMessage;
46  import net.sourceforge.jarduino.message.ArduinoSignal;
47  import net.sourceforge.jarduino.message.ArduinoSignalDefinition;
48  import net.sourceforge.jarduino.message.ArduinoSignalFactor;
49  import net.sourceforge.jarduino.message.ArduinoSignalRange;
50  import net.sourceforge.jarduino.util.ArduinoField;
51  
52  /**
53   * Message Signal Table.
54   */
55  public class ArduinoTableSignal {
56      /**
57       * Colors.
58       */
59      private static final Color[] COLORS = {
60              Color.decode("#FF0000"), Color.decode("#FF7F00"), Color.decode("#FFD400"), Color.decode("#FFFF00"),
61              Color.decode("#BFFF00"), Color.decode("#6AFF00"), Color.decode("#00EAFF"), Color.decode("#0095FF"),
62              Color.decode("#0040FF"), Color.decode("#AA00FF"), Color.decode("#FF00AA"), Color.decode("#EDB9B9"),
63              Color.decode("#E7E9B9"), Color.decode("#B9EDE0"), Color.decode("#B9D7ED"), Color.decode("#DCB9ED"),
64              Color.decode("#8F2323"), Color.decode("#8F6A23"), Color.decode("#4F8F23"), Color.decode("#23628F"),
65              Color.decode("#6B238F"), Color.decode("#000000"), Color.decode("#737373"), Color.decode("#CCCCCC")
66      };
67  
68      /**
69       * Normal border.
70       */
71      private static final Border BORDER_NORMAL = new LineBorder(Color.black);
72  
73      /**
74       * Error border.
75       */
76      private static final Border BORDER_ERROR = new LineBorder(Color.red);
77  
78      /**
79       * Rounding factor (add 0.5 to double before casting to long to get rounding right).
80       */
81      private static final double ROUNDING = 0.5;
82  
83      /**
84       * Small width.
85       */
86      private static final int WIDTH_SMALL = 20;
87  
88      /**
89       * Medium width.
90       */
91      private static final int WIDTH_MEDIUM = 40;
92  
93      /**
94       * Large width.
95       */
96      private static final int WIDTH_LARGE = 150;
97  
98      /**
99       * The owning panel.
100      */
101     private final ArduinoPanelSignal theOwner;
102 
103     /**
104      * The Message table.
105      */
106     private final JTable theTable;
107 
108     /**
109      * The signals list.
110      */
111     private final List<ArduinoSignal> theSignals;
112 
113     /**
114      * The bitMaps list.
115      */
116     private final List<byte[]> theBitMaps;
117 
118     /**
119      * The values Map.
120      */
121     private final Map<ArduinoSignal, Object> theValues;
122 
123     /**
124      * The colours Map.
125      */
126     private final Map<ArduinoSignal, Color> theColours;
127 
128     /**
129      * The model.
130      */
131     private final ArduinoSignalModel theModel;
132 
133     /**
134      * The Multiplex column.
135      */
136     private final TableColumn theMultiCol;
137 
138     /**
139      * Is the multiplex column visible?
140      */
141     private boolean multiVisible;
142 
143     /**
144      * The selected message.
145      */
146     private ArduinoMessage theMessage;
147 
148     /**
149      * Constructor.
150      *
151      * @param pOwner the owning panel
152      */
153     ArduinoTableSignal(final ArduinoPanelSignal pOwner) {
154         /* Store owner */
155         theOwner = pOwner;
156 
157         /* Create components */
158         theTable = new JTable();
159 
160         /* Create the lists and maps */
161         theSignals = new ArrayList<>();
162         theBitMaps = new ArrayList<>();
163         theValues = new HashMap<>();
164         theColours = new HashMap<>();
165 
166         /* Create the table model */
167         theModel = new ArduinoSignalModel();
168         theTable.setModel(theModel);
169 
170         /* Set the row filter */
171         final TableRowSorter<ArduinoSignalModel> mySorter = new TableRowSorter<>(theModel);
172         mySorter.setRowFilter(new MultiFilter());
173         theTable.setRowSorter(mySorter);
174 
175         /* Configure the columns */
176         theMultiCol = configureColumns();
177         multiVisible = true;
178     }
179 
180     /**
181      * Configure the columns.
182      * @return the multiplex column
183      */
184     private TableColumn configureColumns() {
185         /* Create renderers */
186         final TableCellRenderer myRightRenderer = new AlignmentRenderer(SwingConstants.RIGHT);
187         final TableCellRenderer myCenterRenderer = new AlignmentRenderer(SwingConstants.CENTER);
188         final TableCellRenderer myColorRenderer = new ColorRenderer();
189         final TableCellEditor myCellEditor = new DataCellEditor();
190 
191         /* Access the column model */
192         final TableColumnModel myModel = theTable.getColumnModel();
193 
194         /* Set the signal column */
195         TableColumn myColumn = myModel.getColumn(ArduinoSignalModel.COL_SIGNAL);
196         myColumn.setPreferredWidth(WIDTH_LARGE);
197         myColumn.setCellRenderer(myRightRenderer);
198 
199         /* Set the value column */
200         myColumn = myModel.getColumn(ArduinoSignalModel.COL_VALUE);
201         myColumn.setPreferredWidth(WIDTH_MEDIUM);
202         myColumn.setCellRenderer(myCenterRenderer);
203         myColumn.setCellEditor(myCellEditor);
204 
205         /* Set the color column */
206         myColumn = myModel.getColumn(ArduinoSignalModel.COL_COLOR);
207         myColumn.setPreferredWidth(WIDTH_MEDIUM);
208         myColumn.setMaxWidth(WIDTH_MEDIUM);
209         myColumn.setCellRenderer(myColorRenderer);
210 
211         /* Set the startBit column */
212         myColumn = myModel.getColumn(ArduinoSignalModel.COL_START);
213         myColumn.setPreferredWidth(WIDTH_SMALL);
214         myColumn.setMaxWidth(WIDTH_SMALL);
215         myColumn.setCellRenderer(myCenterRenderer);
216 
217         /* Set the length column */
218         myColumn = myModel.getColumn(ArduinoSignalModel.COL_LENGTH);
219         myColumn.setPreferredWidth(WIDTH_SMALL);
220         myColumn.setMaxWidth(WIDTH_SMALL);
221         myColumn.setCellRenderer(myCenterRenderer);
222 
223         /* Set the endian column */
224         myColumn = myModel.getColumn(ArduinoSignalModel.COL_ENDIAN);
225         myColumn.setPreferredWidth(WIDTH_SMALL);
226         myColumn.setMaxWidth(WIDTH_SMALL);
227         myColumn.setCellRenderer(myCenterRenderer);
228 
229         /* Set the units column */
230         myColumn = myModel.getColumn(ArduinoSignalModel.COL_UNITS);
231         myColumn.setPreferredWidth(WIDTH_MEDIUM);
232         myColumn.setCellRenderer(myCenterRenderer);
233 
234         /* Set the factor column */
235         myColumn = myModel.getColumn(ArduinoSignalModel.COL_FACTOR);
236         myColumn.setPreferredWidth(WIDTH_MEDIUM);
237         myColumn.setCellRenderer(myCenterRenderer);
238 
239         /* Set the offset column */
240         myColumn = myModel.getColumn(ArduinoSignalModel.COL_OFFSET);
241         myColumn.setPreferredWidth(WIDTH_MEDIUM);
242         myColumn.setCellRenderer(myCenterRenderer);
243 
244         /* Set the min column */
245         myColumn = myModel.getColumn(ArduinoSignalModel.COL_MIN);
246         myColumn.setPreferredWidth(WIDTH_MEDIUM);
247         myColumn.setCellRenderer(myCenterRenderer);
248 
249         /* Set the max column */
250         myColumn = myModel.getColumn(ArduinoSignalModel.COL_MAX);
251         myColumn.setPreferredWidth(WIDTH_MEDIUM);
252         myColumn.setCellRenderer(myCenterRenderer);
253 
254         /* Handle the multiplex column */
255         myColumn = myModel.getColumn(ArduinoSignalModel.COL_MULTI);
256         myColumn.setPreferredWidth(WIDTH_SMALL);
257         myColumn.setCellRenderer(myCenterRenderer);
258         return myColumn;
259     }
260 
261     /**
262      * Obtain the component.
263      *
264      * @return the component
265      */
266     JComponent getComponent() {
267         return theTable;
268     }
269 
270     /**
271      * Obtain color for index.
272      *
273      * @param pIndex the index
274      * @return the colour
275      */
276     private static Color getColourForIndex(final int pIndex) {
277         return COLORS[pIndex % COLORS.length];
278     }
279 
280     /**
281      * Configure the table.
282      *
283      * @param pMessage the message
284      */
285     void configureTable(final ArduinoMessage pMessage) {
286         /* store the message */
287         theMessage = pMessage;
288 
289         /* Refresh the table */
290         theModel.refresh();
291 
292         /* Determine whether multiplex column should be visible */
293         final boolean bMultiVisible = theMessage.hasMultiplex();
294 
295         /* If we should modify the columns */
296         if (bMultiVisible != multiVisible) {
297             /* Access the table column model */
298             final TableColumnModel myColModel = theTable.getColumnModel();
299 
300             /* If we need to remove the column */
301             if (multiVisible) {
302                 myColModel.removeColumn(theMultiCol);
303 
304                 /* else restore the column and move to correct position */
305             } else {
306                 myColModel.addColumn(theMultiCol);
307                 myColModel.moveColumn(ArduinoSignalModel.COL_MAX, ArduinoSignalModel.COL_MULTI);
308             }
309 
310             /* Flip the flag */
311             multiVisible = !multiVisible;
312         }
313     }
314 
315     /**
316      * Obtain number of rows.
317      *
318      * @return the the number of rows
319      */
320     int getNumRows() {
321         return theSignals.size();
322     }
323 
324     /**
325      * Obtain color for row.
326      *
327      * @param pRowIndex the row index
328      * @return the color
329      */
330     Color getColorForRow(final int pRowIndex) {
331         final ArduinoSignal mySignal = theSignals.get(pRowIndex);
332         return theColours.get(mySignal);
333     }
334 
335     /**
336      * Obtain bitmap for row.
337      *
338      * @param pRowIndex the row index
339      * @return the bitMap
340      */
341     byte[] getBitMapForRow(final int pRowIndex) {
342         return theBitMaps.get(pRowIndex);
343     }
344 
345     /**
346      * loadValues.
347      */
348     void loadValues() {
349         /* Clear the maps */
350         theValues.clear();
351         theColours.clear();
352 
353         /* Loop through the signals */
354         Long myMultiplex = ArduinoSignal.MULTI_NONE;
355         int myColorId = 0;
356         for (ArduinoSignal mySignal : theSignals) {
357             /* If the value is relevant */
358             final Number myMId = mySignal.getMultiplexId();
359             if (myMId.equals(ArduinoSignal.MULTI_NONE)
360                     || myMId.equals(myMultiplex)) {
361                 /* Access the value and store into map */
362                 final Number myValue = getValueForSignal(mySignal);
363                 theValues.put(mySignal, myValue);
364 
365                 /* Store colour for index */
366                 theColours.put(mySignal, getColourForIndex(myColorId++));
367 
368                 /* Store multiplex value if we have it */
369                 if (mySignal.isMultiplex()) {
370                     myMultiplex = myValue.longValue();
371                 }
372             }
373         }
374 
375         /* Update the table */
376         if (theModel != null) {
377             theModel.fireTableDataChanged();
378         }
379     }
380 
381     /**
382      * Obtain value for signal.
383      *
384      * @param pSignal the signal
385      * @return the value
386      */
387     private Number getValueForSignal(final ArduinoSignal pSignal) {
388         /* Access the data buffer */
389         final byte[] myData = theOwner.getData();
390 
391         /* Access the raw value */
392         final ArduinoSignalDefinition myDef = pSignal.getDefinition();
393         final long myValue = myDef.getDataType().isLittleEndian()
394                              ? ArduinoField.parseLEField(myData, myDef.getStartBit(), myDef.getBitLength(), myDef.getDataType().isSigned())
395                              : ArduinoField.parseBEField(myData, myDef.getStartBit(), myDef.getBitLength(), myDef.getDataType().isSigned());
396 
397         /* Apply the adjustment */
398         final ArduinoSignalFactor myFactor = pSignal.getFactor();
399         return myFactor.isFloat()
400                ? (Number) (myFactor.getFactor().doubleValue() * myValue + myFactor.getOffset().doubleValue())
401                : (Number) (myFactor.getFactor().longValue() * myValue + myFactor.getOffset().longValue());
402     }
403 
404     /**
405      * Table Model.
406      */
407     private class ArduinoSignalModel
408             extends AbstractTableModel {
409         /**
410          * Serial Id.
411          */
412         private static final long serialVersionUID = 3030910683228702589L;
413 
414         /**
415          * The signal column.
416          */
417         private static final int COL_SIGNAL = 0;
418 
419         /**
420          * The value column.
421          */
422         private static final int COL_VALUE = COL_SIGNAL + 1;
423 
424         /**
425          * The startBits column.
426          */
427         private static final int COL_START = COL_VALUE + 1;
428 
429         /**
430          * The length column.
431          */
432         private static final int COL_LENGTH = COL_START + 1;
433 
434         /**
435          * The endian column.
436          */
437         private static final int COL_ENDIAN = COL_LENGTH + 1;
438 
439         /**
440          * The multiplex column.
441          */
442         private static final int COL_MULTI = COL_ENDIAN + 1;
443 
444         /**
445          * The color column.
446          */
447         private static final int COL_COLOR = COL_MULTI + 1;
448 
449         /**
450          * The units column.
451          */
452         private static final int COL_UNITS = COL_COLOR + 1;
453 
454         /**
455          * The factor column.
456          */
457         private static final int COL_FACTOR = COL_UNITS + 1;
458 
459         /**
460          * The offset column.
461          */
462         private static final int COL_OFFSET = COL_FACTOR + 1;
463 
464         /**
465          * The minimum column.
466          */
467         private static final int COL_MIN = COL_OFFSET + 1;
468 
469         /**
470          * The maximum column.
471          */
472         private static final int COL_MAX = COL_MIN + 1;
473 
474         /**
475          * Constructor.
476          */
477         ArduinoSignalModel() {
478         }
479 
480         /**
481          * Refresh.
482          */
483         void refresh() {
484             /* Clear the signals */
485             theSignals.clear();
486 
487             /* Add the signals */
488             theSignals.addAll(theMessage.getAllSignals());
489 
490             /* load the values and bitMaps */
491             loadValues();
492             loadBitMaps();
493 
494             /* Refresh the table */
495             fireTableDataChanged();
496         }
497 
498         /**
499          * loadBitMaps.
500          */
501         private void loadBitMaps() {
502             /* Clear the maps */
503             theBitMaps.clear();
504 
505             /* Loop through the signals */
506             for (ArduinoSignal mySignal : theSignals) {
507                 /* Access the value and store into map */
508                 final byte[] myBitMap = getBitMapForSignal(mySignal);
509                 theBitMaps.add(myBitMap);
510             }
511         }
512 
513         @Override
514         public int getRowCount() {
515             return theSignals.size();
516         }
517 
518         @Override
519         public String getColumnName(final int pColIndex) {
520             switch (pColIndex) {
521                 case COL_SIGNAL:
522                     return "Signal";
523                 case COL_VALUE:
524                     return "Value";
525                 case COL_START:
526                     return "S";
527                 case COL_LENGTH:
528                     return "L";
529                 case COL_ENDIAN:
530                     return "E";
531                 case COL_MULTI:
532                     return "M";
533                 case COL_COLOR:
534                     return "Color";
535                 case COL_UNITS:
536                     return "Units";
537                 case COL_FACTOR:
538                     return "Factor";
539                 case COL_OFFSET:
540                     return "Offset";
541                 case COL_MIN:
542                     return "Min";
543                 case COL_MAX:
544                     return "Max";
545                 default:
546                     return null;
547             }
548         }
549 
550         @Override
551         public int getColumnCount() {
552             return COL_MAX + 1;
553         }
554 
555         @Override
556         public boolean isCellEditable(final int pRowIndex,
557                                       final int pColIndex) {
558             final ArduinoSignal mySignal = theSignals.get(pRowIndex);
559             return pColIndex == COL_VALUE && theValues.get(mySignal) != null;
560         }
561 
562         @Override
563         public Object getValueAt(final int pRowIndex,
564                                  final int pColIndex) {
565             final ArduinoSignal mySignal = theSignals.get(pRowIndex);
566             final ArduinoSignalDefinition myDef = mySignal.getDefinition();
567             final ArduinoSignalFactor myFactor = mySignal.getFactor();
568             final ArduinoSignalRange myRange = mySignal.getRange();
569             final long myMId = mySignal.getMultiplexId();
570             switch (pColIndex) {
571                 case COL_SIGNAL:
572                     return mySignal.getName();
573                 case COL_VALUE:
574                     return theValues.get(mySignal);
575                 case COL_START:
576                     return myDef.getStartBit();
577                 case COL_LENGTH:
578                     return myDef.getBitLength();
579                 case COL_ENDIAN:
580                     return myDef.getDataType().isLittleEndian()
581                            ? "L"
582                            : "B";
583                 case COL_MULTI:
584                     if (mySignal.isMultiplex()) {
585                         return "M";
586                     }
587                     return myMId == -1
588                            ? null
589                            : myMId;
590                 case COL_COLOR:
591                     return theColours.get(mySignal);
592                 case COL_UNITS:
593                     return mySignal.getUnits();
594                 case COL_FACTOR:
595                     return myFactor.getFactor();
596                 case COL_OFFSET:
597                     return myFactor.getOffset();
598                 case COL_MIN:
599                     return myRange.unBounded()
600                            ? null
601                            : myRange.getMinimum();
602                 case COL_MAX:
603                     return myRange.unBounded()
604                            ? null
605                            : myRange.getMaximum();
606                 default:
607                     return null;
608             }
609         }
610 
611         @Override
612         public void setValueAt(final Object pValue,
613                                final int pRowIndex,
614                                final int pColIndex) {
615             /* Access signal and set value */
616             final ArduinoSignal mySignal = theSignals.get(pRowIndex);
617             setValueForSignal(mySignal, (Number) pValue);
618 
619             /* reload values */
620             loadValues();
621 
622             /* Update the hex and binary */
623             theOwner.updateHexAndBinary();
624             theOwner.updateBitColors();
625         }
626 
627         /**
628          * Obtain bitMap for signal.
629          *
630          * @param pSignal the signal
631          * @return the value
632          */
633         private byte[] getBitMapForSignal(final ArduinoSignal pSignal) {
634             /* Access the data buffer */
635             final byte[] myData = new byte[theMessage.getLength()];
636 
637             /* Access the raw value */
638             final ArduinoSignalDefinition myDef = pSignal.getDefinition();
639             if (myDef.getDataType().isLittleEndian()) {
640                 ArduinoField.setLEFieldBits(myData, myDef.getStartBit(), myDef.getBitLength());
641             } else {
642                 ArduinoField.setBEFieldBits(myData, myDef.getStartBit(), myDef.getBitLength());
643             }
644 
645             /* Return the bytes */
646             return myData;
647         }
648 
649         /**
650          * Set value for signal.
651          *
652          * @param pSignal the signal
653          * @param pValue  the value
654          */
655         private void setValueForSignal(final ArduinoSignal pSignal,
656                                        final Number pValue) {
657             /* Access the data buffer */
658             final byte[] myData = theOwner.getData();
659 
660             /* Apply the adjustment */
661             final ArduinoSignalFactor myFactor = pSignal.getFactor();
662             final long myValue = myFactor.isFloat()
663                                  ? (long) (ROUNDING + ((pValue.doubleValue() - myFactor.getOffset().doubleValue()) / myFactor.getFactor().doubleValue()))
664                                  : (pValue.longValue() - myFactor.getOffset().longValue()) / myFactor.getFactor().longValue();
665 
666             /* Set the raw value */
667             final ArduinoSignalDefinition myDef = pSignal.getDefinition();
668             if (myDef.getDataType().isLittleEndian()) {
669                 ArduinoField.setLEField(myData, myDef.getStartBit(), myDef.getBitLength(), myValue);
670             } else {
671                 ArduinoField.setBEField(myData, myDef.getStartBit(), myDef.getBitLength(), myValue);
672             }
673         }
674     }
675 
676     /**
677      * Color Renderer.
678      */
679     private static class ColorRenderer extends DefaultTableCellRenderer {
680         @Override
681         public void setValue(final Object pValue) {
682             setBackground((Color) pValue);
683         }
684     }
685 
686     /**
687      * Simple Alignment Renderer.
688      */
689     private class AlignmentRenderer extends DefaultTableCellRenderer {
690         /**
691          * Serial Id.
692          */
693         private static final long serialVersionUID = -6387123991399458414L;
694 
695         /**
696          * Number format.
697          */
698         private final NumberFormat myFormatter = NumberFormat.getInstance();
699 
700         /**
701          * Constructor.
702          *
703          * @param pAlignment the alignment
704          */
705         AlignmentRenderer(final int pAlignment) {
706             setHorizontalAlignment(pAlignment);
707         }
708 
709         @Override
710         public JComponent getTableCellRendererComponent(final JTable pTable,
711                                                         final Object pValue,
712                                                         final boolean pSelected,
713                                                         final boolean hasFocus,
714                                                         final int pRow,
715                                                         final int pCol) {
716             /* Determine the active signal */
717             final int myRow = pTable.convertRowIndexToModel(pRow);
718             final ArduinoSignal mySignal = theSignals.get(myRow);
719 
720             /* Obtain the string representation */
721             String myValue = pValue == null
722                              ? null
723                              : pValue.toString();
724 
725             /* If this is a double */
726             if (pValue instanceof Double) {
727                 /* Find out how many decimals we have */
728                 final int myNumDecimals = mySignal.getFactor().getNumDecimals();
729                 myFormatter.setMaximumFractionDigits(myNumDecimals);
730                 myFormatter.setMinimumFractionDigits(myNumDecimals);
731 
732                 /* Truncate the decimals */
733                 myValue = myFormatter.format(((Double) pValue).doubleValue());
734             }
735 
736             /* Set the value */
737             setText(myValue);
738 
739             /* return this as the component */
740             return this;
741         }
742     }
743 
744     /**
745      * Data Cell Editor.
746      */
747     private class DataCellEditor
748             extends AbstractCellEditor
749             implements TableCellEditor {
750         /**
751          * Serial Id.
752          */
753         private static final long serialVersionUID = 7142514324836088035L;
754 
755         /**
756          * Are we in the middle of stopping edit?
757          */
758         private final JTextField theEditor;
759 
760         /**
761          * Number format.
762          */
763         private final NumberFormat myFormatter = NumberFormat.getInstance();
764 
765         /**
766          * The signal.
767          */
768         private transient ArduinoSignal theSignal;
769 
770         /**
771          * The edited value.
772          */
773         private Number theValue;
774 
775         /**
776          * Are we in the middle of stopping edit?
777          */
778         private boolean stoppingEdit;
779 
780         /**
781          * Constructor.
782          */
783         DataCellEditor() {
784             theEditor = new JTextField();
785             theEditor.setHorizontalAlignment(SwingConstants.CENTER);
786             theEditor.addFocusListener(new FocusListener() {
787                 @Override
788                 public void focusLost(final FocusEvent pE) {
789                     /* Do nothing */
790                 }
791                 @Override
792                 public void focusGained(final FocusEvent pE) {
793                     theEditor.selectAll();
794                 }
795             });
796         }
797 
798         @Override
799         public Number getCellEditorValue() {
800             return theValue;
801         }
802 
803         @Override
804         public Component getTableCellEditorComponent(final JTable pTable,
805                                                      final Object pValue,
806                                                      final boolean pSelected,
807                                                      final int pRow,
808                                                      final int pCol) {
809             /* Determine the active signal */
810             final int myRow = pTable.convertRowIndexToModel(pRow);
811             theSignal = theSignals.get(myRow);
812 
813             /* Format the value */
814             String myValue = null;
815             if (pValue instanceof Double) {
816                 /* Find out how many decimals we have */
817                 final int myNumDecimals = theSignal.getFactor().getNumDecimals();
818                 myFormatter.setMaximumFractionDigits(myNumDecimals);
819                 myFormatter.setMinimumFractionDigits(myNumDecimals);
820                 myValue = myFormatter.format(((Number) pValue).doubleValue());
821             } else if (pValue instanceof Long) {
822                 myValue = pValue.toString();
823             }
824 
825             /* Set field value and start edit */
826             theEditor.setText(myValue);
827             theEditor.setBorder(BORDER_NORMAL);
828 
829             /* Return the field */
830             return theEditor;
831         }
832 
833         @Override
834         public boolean stopCellEditing() {
835             /* If we are already stopping edit */
836             if (stoppingEdit) {
837                 return true;
838             }
839 
840             /* Parse the data */
841             stoppingEdit = true;
842             try {
843                 theValue = myFormatter.parse(theEditor.getText());
844 
845                 /* Catch parse exceptions */
846             } catch (ParseException e) {
847                 /* Return status and set error border */
848                 stoppingEdit = false;
849                 theEditor.setBorder(BORDER_ERROR);
850                 return false;
851             }
852 
853             /* Make sure that the value is the correct dataType */
854             if (theValue instanceof Long && theSignal.isFloat()) {
855                 theValue = theValue.doubleValue();
856             }
857             if (theValue instanceof Double && !theSignal.isFloat()) {
858                 theValue = theValue.longValue();
859             }
860 
861             /* If there is no error */
862             boolean bComplete = false;
863             if (checkValueForSignal(theSignal, theValue)) {
864                 /* Pass call onwards */
865                 bComplete = super.stopCellEditing();
866 
867                 /* else set error border */
868             } else {
869                 theEditor.setBorder(BORDER_ERROR);
870             }
871 
872             /* Return status */
873             stoppingEdit = false;
874             return bComplete;
875         }
876 
877         /**
878          * Check value for signal.
879          *
880          * @param pSignal the signal
881          * @param pValue  the value
882          * @return valid true/false
883          */
884         private boolean checkValueForSignal(final ArduinoSignal pSignal,
885                                             final Number pValue) {
886             /* If we are unbounded, just return OK */
887             final ArduinoSignalRange myRange = pSignal.getRange();
888             if (myRange.unBounded()) {
889                 return true;
890             }
891 
892             /* Check the value */
893             return pValue instanceof Double
894                    ? pValue.doubleValue() >= myRange.getMinimum().doubleValue()
895                            && pValue.doubleValue() <= myRange.getMaximum().doubleValue()
896                    : pValue.longValue() >= myRange.getMinimum().longValue()
897                            && pValue.longValue() <= myRange.getMaximum().longValue();
898         }
899     }
900 
901     /**
902      * RowFilter class.
903      */
904     private class MultiFilter extends RowFilter<ArduinoSignalModel, Integer> {
905         @Override
906         public boolean include(final Entry<? extends ArduinoSignalModel, ? extends Integer> entry) {
907             /* Always show if this message has no multiplex */
908             final ArduinoSignal myMulti = theMessage.getMultiplexSignal();
909             if (myMulti == null) {
910                 return true;
911             }
912 
913             /* Access the signal and show if it is non-multiplex or matches the multiplex */
914             final ArduinoSignal mySignal = theSignals.get(entry.getIdentifier());
915             final Long myMId = mySignal.getMultiplexId();
916             return myMId.equals(ArduinoSignal.MULTI_NONE)
917                     || myMId.equals(theValues.get(myMulti));
918         }
919     }
920 }