001/* A graphical component displaying an array of records.
002
003 Copyright (c) 1997-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027
028 */
029package ptolemy.actor.gui;
030
031import java.awt.Component;
032import java.awt.Dimension;
033import java.util.Iterator;
034import java.util.LinkedList;
035import java.util.List;
036
037import javax.swing.JPanel;
038import javax.swing.JScrollPane;
039import javax.swing.JTable;
040import javax.swing.table.AbstractTableModel;
041import javax.swing.table.JTableHeader;
042import javax.swing.table.TableCellRenderer;
043import javax.swing.table.TableColumn;
044
045import ptolemy.data.ArrayToken;
046import ptolemy.data.RecordToken;
047import ptolemy.data.StringToken;
048import ptolemy.data.Token;
049import ptolemy.kernel.util.IllegalActionException;
050import ptolemy.kernel.util.InternalErrorException;
051
052///////////////////////////////////////////////////////////////////
053//// ArrayOfRecordsPane
054
055/**
056 A graphical component that displays the values in an array of records.
057 The data to display is supplied in the form of an ArrayToken.
058 The table is currently not editable, although this may be changed
059 in the future.  The table is an instance of Java's JTable class,
060 which is exposed as a public member.  The rich interface
061 of the JTable class can be used to customize the display
062 in various ways.
063
064 @author Edward A. Lee
065 @version $Id$
066 @since Ptolemy II 8.0
067 @Pt.ProposedRating Red (eal)
068 @Pt.AcceptedRating Red (eal)
069 */
070@SuppressWarnings("serial")
071public class ArrayOfRecordsPane extends JPanel {
072    /** Construct an empty table pane.
073     */
074    public ArrayOfRecordsPane() {
075        super();
076        table = new SimpleTable();
077        // FIXME: As usual with Swing, the scrollpane doesn't appear.
078        // But if we don't do this, then the table headers don't appear!
079        // Go figure...
080        JScrollPane scrollPane = new JScrollPane(table);
081        add(scrollPane);
082    }
083
084    ///////////////////////////////////////////////////////////////////
085    ////                         public methods                    ////
086
087    /** Clear the display. */
088    public void clear() {
089        table.setModel(_emptyTableModel);
090    }
091
092    /** Set the array to display in the table.
093     *  This method results in all fields of the records being displayed.
094     *  @param array The array of records to display in the table.
095     */
096    public void display(ArrayToken array) {
097        display(array, null);
098    }
099
100    /** Set the array to display in the table.
101     *  @param array The array of records to display in the table.
102     *  @param columns The array of strings giving the column names
103     *   to display.
104     */
105    public void display(ArrayToken array, ArrayToken columns) {
106        table.setModel(new ArrayAsTable(array, columns));
107        table.setTableHeader(new JTableHeader(table.getColumnModel()));
108        _initColumnSizes(table);
109    }
110
111    ///////////////////////////////////////////////////////////////////
112    ////                         public variables                  ////
113
114    /** The table representing the matrix. */
115    public JTable table;
116
117    ///////////////////////////////////////////////////////////////////
118    ////                         private methods                   ////
119
120    /** This method picks good column sizes and sets the preferred
121     *  size for the table.
122     *  It is adapted from the Java Tutorials from Sun Microsystems.
123     */
124    private void _initColumnSizes(JTable table) {
125        ArrayAsTable model = (ArrayAsTable) table.getModel();
126        TableColumn column = null;
127        Component component = null;
128        TableCellRenderer headerRenderer = table.getTableHeader()
129                .getDefaultRenderer();
130
131        int tableWidth = 0;
132        int tableHeight = 0;
133        for (int i = 0; i < model.getColumnCount(); i++) {
134            column = table.getColumnModel().getColumn(i);
135            component = headerRenderer.getTableCellRendererComponent(null,
136                    column.getHeaderValue(), false, false, 0, 0);
137            int width = component.getPreferredSize().width;
138            int columnHeight = 0;
139            for (int j = 0; j < model.getRowCount(); j++) {
140                component = table.getDefaultRenderer(model.getColumnClass(i))
141                        .getTableCellRendererComponent(table,
142                                model.getValueAt(j, i), false, false, 0, i);
143                int cellWidth = component.getPreferredSize().width;
144                if (cellWidth > width) {
145                    width = cellWidth;
146                }
147                columnHeight += component.getPreferredSize().height;
148            }
149            column.setPreferredWidth(width);
150            tableWidth += width;
151
152            if (columnHeight > tableHeight) {
153                tableHeight = columnHeight;
154            }
155        }
156        Dimension tableSize = new Dimension(tableWidth, tableHeight);
157        table.setPreferredSize(tableSize);
158    }
159
160    ///////////////////////////////////////////////////////////////////
161    ////                         private variables                 ////
162
163    /** Empty table model. */
164    private static EmptyTableModel _emptyTableModel = new EmptyTableModel();
165
166    ///////////////////////////////////////////////////////////////////
167    ////                         inner class                       ////
168
169    /** This class provides an implementation of the
170     *  TableModel interface for viewing an array of records.
171     */
172    public static class ArrayAsTable extends AbstractTableModel {
173        // FindBugs suggests making this class static so as to decrease
174        // the size of instances and avoid dangling references.
175
176        /** Construct a table for the specified array to display
177         *  all fields in the records contained by the array.
178         *  @param array An array of record tokens to display.
179         */
180        ArrayAsTable(ArrayToken array) {
181            this(array, null);
182        }
183
184        /** Construct a table for the specified array to display
185         *  the fields given by <i>columns</i> of records in the specified
186         *  <i>array</i>.
187         *  @param array An array of record tokens to display.
188         *  @param columns An array of string tokens giving the names
189         *   of fields to display, or null to display all the fields.
190         */
191        ArrayAsTable(ArrayToken array, ArrayToken columns) {
192            _array = array;
193
194            if (columns == null) {
195                // Figure out what the column names are.
196                for (int i = 0; i < _array.length(); i++) {
197                    RecordToken record = (RecordToken) _array.getElement(i);
198                    Iterator labels = record.labelSet().iterator();
199                    while (labels.hasNext()) {
200                        String column = (String) labels.next();
201                        if (!_columns.contains(column)) {
202                            _columns.add(column);
203                        }
204                    }
205                }
206            } else {
207                for (int i = 0; i < columns.length(); i++) {
208                    StringToken column = (StringToken) columns.getElement(i);
209                    _columns.add(column.stringValue());
210                }
211            }
212        }
213
214        ///////////////////////////////////////////////////////////////////
215        ////                         public methods                    ////
216
217        /** Get the column count of the Matrix.
218         *  @return the column count.
219         */
220        @Override
221        public int getColumnCount() {
222            return _columns.size();
223        }
224
225        /** Get the name of the specified column, which is the column
226         *  index as a string.
227         *  @param columnIndex The index of the column.
228         *  @return The column index as a string.
229         */
230        @Override
231        public String getColumnName(int columnIndex) {
232            if (columnIndex > _columns.size()) {
233                return "";
234            }
235            return _columns.get(columnIndex);
236        }
237
238        /** Get the row count of the Matrix.
239         *  @return the row count.
240         */
241        @Override
242        public int getRowCount() {
243            return _array.length();
244        }
245
246        /** Get the specified entry as a String.
247         *  @param row The row number.
248         *  @param column The column number.
249         *  @return An instance of Token representing the matrix value
250         *   at the specified row and column.
251         */
252        @Override
253        public Object getValueAt(int row, int column) {
254            // There is a bug in JTable, where it happily tries to access
255            // rows and columns that are outside of range.
256            if (row >= _array.length() || column >= _columns.size()) {
257                return "";
258            }
259            Token element = ((RecordToken) _array.getElement(row))
260                    .get(_columns.get(column));
261            if (element == null) {
262                return "";
263            }
264            // Strip off the extra quotation marks if necessary.
265            if (element instanceof StringToken) {
266                return ((StringToken) element).stringValue();
267            }
268            return element.toString();
269        }
270
271        /** Remove the specified row from the table.
272         *  If the row is out of range, do nothing.
273         *  @param row The row to remove, starting with index 0.
274         */
275        public void removeRow(int row) {
276            if (row < _array.length()) {
277                // Since tokens are immutable, we have to create a whole new token.
278                Token[] newArray = new Token[_array.length() - 1];
279                for (int i = 0; i < row; i++) {
280                    newArray[i] = _array.getElement(i);
281                }
282                for (int i = row + 1; i < _array.length(); i++) {
283                    newArray[i - 1] = _array.getElement(i);
284                }
285                try {
286                    ArrayToken newToken = new ArrayToken(newArray);
287                    _array = newToken;
288                    super.fireTableRowsDeleted(row, row);
289                } catch (IllegalActionException e) {
290                    throw new InternalErrorException(e);
291                }
292            }
293        }
294
295        ///////////////////////////////////////////////////////////////////
296        ////                         private members                   ////
297
298        /** The array for which a Table Model is created. */
299        private ArrayToken _array = null;
300
301        /** The column names found in the array. */
302        private List<String> _columns = new LinkedList<String>();
303    }
304
305    /** This class provides an implementation of the
306     *  TableModel interface representing an empty table.
307     *  This is used to clear the display.
308     */
309    private static class EmptyTableModel extends AbstractTableModel {
310        ///////////////////////////////////////////////////////////////////
311        ////                         public methods                    ////
312
313        /** Get the column count of the Matrix.
314         *  @return Zero.
315         */
316        @Override
317        public int getColumnCount() {
318            return 0;
319        }
320
321        /** Get the row count of the Matrix.
322         *  @return Zero.
323         */
324        @Override
325        public int getRowCount() {
326            return 0;
327        }
328
329        /** Get the specified entry from the matrix as a String.
330         *  @param row The row number.
331         *  @param column The column number.
332         *  @return An empty String.
333         */
334        @Override
335        public Object getValueAt(int row, int column) {
336            return "";
337        }
338    }
339
340    /** Table panel. */
341    private static class SimpleTable extends JTable {
342        public SimpleTable() {
343            super();
344            // Adjust column widths automatically.
345            setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
346
347            // FIXME: Don't hardwire the size here ?
348            // Sadly, as usual with swing, the preferred size of the table has
349            // no effect. Also, the scrollbar does not appear... This is really lame...
350            setPreferredScrollableViewportSize(new Dimension(800, 200));
351            // This is Java 1.6 specific:
352            // table.setFillsViewportHeight(true);
353        }
354    }
355}