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.Image;
20  import java.awt.event.MouseAdapter;
21  import java.awt.event.MouseEvent;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.List;
25  import javax.imageio.ImageIO;
26  import javax.swing.Icon;
27  import javax.swing.ImageIcon;
28  import javax.swing.JComponent;
29  import javax.swing.JTable;
30  import javax.swing.RowSorter.SortKey;
31  import javax.swing.SortOrder;
32  import javax.swing.SwingConstants;
33  import javax.swing.table.AbstractTableModel;
34  import javax.swing.table.DefaultTableCellRenderer;
35  import javax.swing.table.TableCellRenderer;
36  import javax.swing.table.TableColumn;
37  import javax.swing.table.TableColumnModel;
38  import javax.swing.table.TableRowSorter;
39  
40  import net.sourceforge.jarduino.message.ArduinoMessage;
41  import net.sourceforge.jarduino.message.ArduinoMsgFilter;
42  import net.sourceforge.jarduino.message.ArduinoNamedObject;
43  import net.sourceforge.jarduino.message.ArduinoSystem;
44  import net.sourceforge.jarduino.util.ArduinoLogManager;
45  import net.sourceforge.jarduino.util.ArduinoLogger;
46  
47  /**
48   * Arduino Message Table.
49   */
50  public class ArduinoTableFilter {
51      /**
52       * The LOGGER.
53       */
54      private static final ArduinoLogger LOGGER = ArduinoLogManager.getLogger(ArduinoTableFilter.class);
55  
56      /**
57       * The tick icon.
58       */
59      private static final Icon TICKICON = loadIcon("GreenJellyTick.png");
60  
61      /**
62       * The cross icon.
63       */
64      private static final Icon CROSSICON = loadIcon("OrangeJellyCross.png");
65  
66      /**
67       * Icon Size.
68       */
69      private static final int ICON_SIZE = 20;
70  
71      /**
72       * Small width.
73       */
74      private static final int WIDTH_SMALL = 60;
75  
76      /**
77       * Medium width.
78       */
79      private static final int WIDTH_MEDIUM = 80;
80  
81      /**
82       * Large width.
83       */
84      private static final int WIDTH_LARGE = 150;
85  
86      /**
87       * The parent panel.
88       */
89      private final ArduinoPanelFilter theParent;
90  
91      /**
92       * The table.
93       */
94      private final JTable theTable;
95  
96      /**
97       * The table model.
98       */
99      private final ArduinoFilterModel theModel;
100 
101     /**
102      * The message list.
103      */
104     private final ArduinoMsgFilter theMsgFilter;
105 
106     /**
107      * The filter.
108      */
109     private List<ArduinoMessage> theMessages;
110 
111     /**
112      * Are we using a bespoke filter.
113      */
114     private boolean isBespoke;
115 
116     /**
117      * Constructor.
118      * @param pParent the parent panel
119      */
120     ArduinoTableFilter(final ArduinoPanelFilter pParent) {
121         /* Store parameters */
122         theParent = pParent;
123 
124         /* Create the filter */
125         theMsgFilter = new ArduinoMsgFilter();
126 
127         /* Create the table and model */
128         theTable = new JTable();
129         theModel = new ArduinoFilterModel();
130         theTable.setModel(theModel);
131         theTable.addMouseListener(new MouseListener());
132 
133         /* Set the row sorter */
134         theModel.setSorter();
135 
136         /* Configure the columns */
137         configureColumns();
138     }
139 
140     /**
141      * Obtain the component.
142      * @return the component
143      */
144     JComponent getComponent() {
145         return theTable;
146     }
147 
148 
149     /**
150      * Obtain the filter.
151      * @return the filter
152      */
153     ArduinoMsgFilter getFilter() {
154         return theMsgFilter;
155     }
156 
157     /**
158      * Configure the columns.
159      */
160     private void configureColumns() {
161         /* Create renderers */
162         final TableCellRenderer myRightRenderer = new AlignmentRenderer(SwingConstants.RIGHT);
163         final TableCellRenderer myCenterRenderer = new AlignmentRenderer(SwingConstants.CENTER);
164         final TableCellRenderer myIconRenderer = new IconRenderer();
165 
166         /* Access the column model */
167         final TableColumnModel myModel = theTable.getColumnModel();
168 
169         /* Set the name column */
170         TableColumn myColumn = myModel.getColumn(ArduinoFilterModel.COL_NAME);
171         myColumn.setPreferredWidth(WIDTH_LARGE);
172         myColumn.setCellRenderer(myRightRenderer);
173 
174         /* Set the id column */
175         myColumn = myModel.getColumn(ArduinoFilterModel.COL_ID);
176         myColumn.setPreferredWidth(WIDTH_MEDIUM);
177         myColumn.setCellRenderer(myCenterRenderer);
178 
179         /* Set the received column */
180         myColumn = myModel.getColumn(ArduinoFilterModel.COL_RECEIVED);
181         myColumn.setPreferredWidth(WIDTH_SMALL);
182         myColumn.setMaxWidth(WIDTH_SMALL);
183         myColumn.setCellRenderer(myIconRenderer);
184 
185         /* Set the sent column */
186         myColumn = myModel.getColumn(ArduinoFilterModel.COL_SENT);
187         myColumn.setPreferredWidth(WIDTH_SMALL);
188         myColumn.setMaxWidth(WIDTH_SMALL);
189         myColumn.setCellRenderer(myIconRenderer);
190     }
191 
192     /**
193      * Configure the table.
194      *
195      * @param pSystem the system
196      */
197     void configureForSystem(final ArduinoSystem pSystem) {
198         /* store the details */
199         theMessages = pSystem.getMessages();
200 
201         /* Configure for the system node */
202         configureForNode(pSystem);
203     }
204 
205     /**
206      * Configure the table.
207      *
208      * @param pNode the node
209      */
210     void configureForNode(final ArduinoNamedObject pNode) {
211         /* Update the filter */
212         theMsgFilter.resetSelection(pNode);
213 
214         /* Reset bespoke flag */
215         isBespoke = false;
216 
217         /* update the table model */
218         theModel.fireTableDataChanged();
219         theParent.updateFilter();
220     }
221 
222     /**
223      * Is the filter bespoke?
224      * @return true/false
225      */
226     boolean isBespoke() {
227         return isBespoke;
228     }
229 
230     /**
231      * Load icon.
232      * @param pName the name of the icon
233      * @return the icon
234      */
235     private static Icon loadIcon(final String pName) {
236         /* Load the icon */
237         try {
238             final Image myImage = ImageIO.read(ArduinoTableFilter.class.getResourceAsStream("icons/" + pName));
239             final Image myNewImage = myImage.getScaledInstance(ICON_SIZE, ICON_SIZE, Image.SCALE_SMOOTH);
240             return new ImageIcon(myNewImage);
241         } catch (IOException e) {
242             LOGGER.error("Failed to load icon", e);
243             return null;
244         }
245     }
246 
247     /**
248      * Table Model.
249      */
250     private class ArduinoFilterModel
251             extends AbstractTableModel {
252         /**
253          * Serial Id.
254          */
255         private static final long serialVersionUID = 240324436537632666L;
256 
257         /**
258          * The name column.
259          */
260         private static final int COL_NAME = 0;
261 
262         /**
263          * The id column.
264          */
265         private static final int COL_ID = COL_NAME + 1;
266 
267         /**
268          * The sent column.
269          */
270         private static final int COL_SENT = COL_ID + 1;
271 
272         /**
273          * The received column.
274          */
275         private static final int COL_RECEIVED = COL_SENT + 1;
276 
277         /**
278          * Constructor.
279          */
280         ArduinoFilterModel() {
281         }
282 
283         @Override
284         public int getRowCount() {
285             return theMessages != null ? theMessages.size() : 0;
286         }
287 
288         @Override
289         public String getColumnName(final int pColIndex) {
290             switch (pColIndex) {
291                 case COL_NAME:
292                     return "Name";
293                 case COL_ID:
294                     return "Id";
295                 case COL_SENT:
296                     return "Sent";
297                 case COL_RECEIVED:
298                     return "Received";
299                 default:
300                     return null;
301             }
302         }
303 
304         @Override
305         public int getColumnCount() {
306             return COL_RECEIVED + 1;
307         }
308 
309         @Override
310         public boolean isCellEditable(final int pRowIndex,
311                                       final int pColIndex) {
312             return false;
313         }
314 
315         @Override
316         public Object getValueAt(final int pRowIndex,
317                                  final int pColIndex) {
318             switch (pColIndex) {
319                 case COL_NAME:
320                     return theMessages.get(pRowIndex).getName();
321                 case COL_ID:
322                     return getId(pRowIndex);
323                 case COL_SENT:
324                     return theMsgFilter.isBuilt(getId(pRowIndex)) ? TICKICON : CROSSICON;
325                 case COL_RECEIVED:
326                     return theMsgFilter.isParsed(getId(pRowIndex)) ? TICKICON : CROSSICON;
327                 default:
328                     return null;
329             }
330         }
331 
332         /**
333          * Obtain the id for a row.
334          * @param pRowIndex the rowIndex
335          * @return the id.
336          */
337         private String getId(final int pRowIndex) {
338             return theMessages.get(pRowIndex).getId();
339         }
340 
341         /**
342          * Set row sorter.
343          */
344         void setSorter() {
345             /* Create a sorter */
346             final TableRowSorter<ArduinoFilterModel> mySorter = new TableRowSorter<>(this);
347 
348             /* Create the standard sort order */
349             final List<SortKey> mySortKeys = new ArrayList<>();
350             mySortKeys.add(new SortKey(COL_SENT, SortOrder.ASCENDING));
351             mySortKeys.add(new SortKey(COL_RECEIVED, SortOrder.ASCENDING));
352             mySorter.setSortKeys(mySortKeys);
353 
354             /* Sort on update and set sorter */
355             mySorter.setSortsOnUpdates(true);
356             theTable.setRowSorter(mySorter);
357         }
358     }
359 
360     /**
361      * Simple Alignment Renderer.
362      */
363     private static class AlignmentRenderer extends DefaultTableCellRenderer {
364         /**
365          * Constructor.
366          *
367          * @param pAlignment the alignment
368          */
369         AlignmentRenderer(final int pAlignment) {
370             setHorizontalAlignment(pAlignment);
371         }
372 
373         @Override
374         public JComponent getTableCellRendererComponent(final JTable pTable,
375                                                         final Object pValue,
376                                                         final boolean pSelected,
377                                                         final boolean hasFocus,
378                                                         final int pRow,
379                                                         final int pCol) {
380             /* Obtain the string representation */
381             final String myValue = pValue == null
382                              ? null
383                              : pValue.toString();
384 
385             /* Set the value */
386             setText(myValue);
387 
388             /* return this as the component */
389             return this;
390         }
391     }
392 
393     /**
394      * Simple Icon Renderer.
395      */
396     private static class IconRenderer extends DefaultTableCellRenderer {
397         /**
398          * Constructor.
399          */
400         IconRenderer() {
401             setHorizontalAlignment(SwingConstants.CENTER);
402         }
403 
404         @Override
405         public JComponent getTableCellRendererComponent(final JTable pTable,
406                                                         final Object pValue,
407                                                         final boolean pSelected,
408                                                         final boolean hasFocus,
409                                                         final int pRow,
410                                                         final int pCol) {
411             /* Access the icon */
412             final Icon myIcon = pValue instanceof Icon
413                                    ? (Icon) pValue
414                                    : null;
415 
416             /* Set the icon */
417             setIcon(myIcon);
418 
419             /* return this as the component */
420             return this;
421         }
422     }
423 
424     /**
425      * Mouse Adapter class.
426      * <p>
427      * Required to handle button clicked, dragged, and released in different place
428      */
429     private class MouseListener
430             extends MouseAdapter {
431         @Override
432         public void mouseClicked(final MouseEvent e) {
433             /* Determine the row and column that the click has occurred in */
434             final int myRow = theTable.convertRowIndexToModel(theTable.rowAtPoint(e.getPoint()));
435             final int myCol = theTable.convertColumnIndexToModel(theTable.columnAtPoint(e.getPoint()));
436 
437             /* If it was in the table and is a column of interest */
438             if (myRow != -1
439                     && (myCol == ArduinoFilterModel.COL_RECEIVED || myCol == ArduinoFilterModel.COL_SENT)) {
440                 /* Process the click */
441                 final ArduinoMessage myMessage = theMessages.get(myRow);
442                 processClick(myMessage, myCol);
443                 theModel.fireTableCellUpdated(myRow, myCol);
444                 theParent.updateFilter();
445             }
446         }
447 
448         /**
449          * Process click.
450          * @param pMessage the message
451          * @param pColumn the column index
452          */
453         void processClick(final ArduinoMessage pMessage,
454                           final int pColumn) {
455             final int myFlag = pColumn == ArduinoFilterModel.COL_RECEIVED
456                                ? ArduinoMsgFilter.PARSEMSG
457                                : ArduinoMsgFilter.BUILDMSG;
458             theMsgFilter.toggleFlag(pMessage.getId(), myFlag);
459             isBespoke = true;
460         }
461     }
462 }