diff -r 077ebe3d0d24 -r f1d133b09a8c components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/swing/ExtTable.java --- a/components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/swing/ExtTable.java Tue Dec 16 05:53:51 2014 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,922 +0,0 @@ -/* - * CDDL HEADER START - * - * The contents of this file are subject to the terms of the - * Common Development and Distribution License (the "License"). - * You may not use this file except in compliance with the License. - * - * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE - * or http://www.opensolaris.org/os/licensing. - * See the License for the specific language governing permissions - * and limitations under the License. - * - * When distributing Covered Code, include this CDDL HEADER in each - * file and include the License file at usr/src/OPENSOLARIS.LICENSE. - * If applicable, add the following below this CDDL HEADER, with the - * fields enclosed by brackets "[]" replaced with your own identifying - * information: Portions Copyright [yyyy] [name of copyright owner] - * - * CDDL HEADER END - */ - -/* - * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. - */ - -package com.oracle.solaris.vp.util.swing; - -import java.awt.*; -import java.awt.event.*; -import java.util.*; -import java.util.List; -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.event.*; -import javax.swing.plaf.TableUI; -import javax.swing.table.*; - -/** - * The {@code ExtTable} class is a {@code JTable} with the following extras: - * - *

Striping

- * - * The {@code ExtTable} is decorated by default with alternating colored rows: - * 1) the table's background, and 2) {@link #setStripeColor the stripe color}. - * To turn this behavior off, call {@link #setStripeColor setStripeColor}{@code - * (null)}. - * - *

Caching

- * - * The {@code ExtTable} can optionally cache the values it finds in its - * {@code TableModel}. This can speed performance when the model's {@code - * getValueAt} method is time-consuming. This feature is off by default, but - * can be enabled by {@link #setCacheSize setting the size of the cache} to a - * non-zero number of rows. - * - *

Specifiable visible row count

- * - * This feature, found in the {@code JList} API, {@link #setVisibleRowCount - * sets} the preferred number of rows to display without requiring - * scrolling. This feature is off by default. - * - *

Automatically-sized column widths

- * - * Columns in an {@code ExtTable} can be easily sized to fit their content, - * overriding the constant column widths set by {@code JTable}. This can be - * done {@link #fitColumns on demand} or {@link #setAutoFitColumns - * automatically} whenever the table changes. This feature is off by default. - */ -@SuppressWarnings({"serial"}) -public class ExtTable extends JTable { - // - // Enums - // - - public enum ColumnWidthPolicy { - /** - * Column width is the maximum preferred width of each cell and - * header in the column. - */ - PREFERRED, - - /** - * Columns widths are sized equally, based on the maximum value of all - * columns' preferred widths. - */ - EQUAL - } - - // - // Inner classes - // - - private static class CachedColumn { - // - // Instance data - // - - public int col; - public T value; - - // - // Constructors - // - - public CachedColumn(int col, T value) { - this.col = col; - this.value = value; - } - } - - private static class CachedRow { - // - // Instance data - // - - public int row; - public List> cols = new LinkedList>(); - - // - // Constructors - // - - public CachedRow(int row) { - this.row = row; - } - } - - // - // Static data - // - - public static final Color DEFAULT_STRIPE_COLOR = - new Color(242, 242, 242); -// new Color(237, 243, 254); - - // - // Instance data - // - - private int visibleRowCount = -1; - private Color stripeColor = DEFAULT_STRIPE_COLOR; - - private List> valueCache = - new LinkedList>(); - private int valueCacheSize = 0; - - private Map> widthCache = - new HashMap>(); - private ColumnWidthPolicy colWidthPolicy; - - private TableModelListener modelListener; - private TableColumnModelListener columnListener; - private RowSorterListener sorterListener; - - // - // Constructors - // - - public ExtTable() { - init(); - } - - public ExtTable(int numRows, int numColumns) { - super(numRows, numColumns); - init(); - } - - public ExtTable(Object[][] rowData, Object[] columnNames) { - super(rowData, columnNames); - init(); - } - - public ExtTable(TableModel model) { - super(model); - init(); - } - - public ExtTable(TableModel model, TableColumnModel cModel) { - super(model, cModel); - init(); - } - - public ExtTable(TableModel model, TableColumnModel cModel, - ListSelectionModel sModel) { - - super(model, cModel, sModel); - init(); - } - - public ExtTable(Vector rowData, Vector columnNames) { - super(rowData, columnNames); - init(); - } - - // - // JComponent methods - // - - @Override - protected void paintComponent(Graphics g) { - Rectangle clip = g.getClipBounds(); - int rHeight = getRowHeight(); - int fRow = clip.y / rHeight; - int lRow = (clip.y + clip.height) / rHeight + 1; - - if (isOpaque()) { - g.setColor(getBackground()); - g.fillRect(clip.x, clip.y, clip.width, clip.height); - } - - if (stripeColor != null) { - g.setColor(stripeColor); - if ((fRow & 1) == 1) { - fRow++; - } - - for (int row = fRow; row < lRow; row += 2) { - g.fillRect(clip.x, rHeight * row, clip.width, rHeight); - } - } - - if (getShowHorizontalLines()) { - g.setColor(getGridColor()); - for (int row = fRow; row < lRow; row++) { - int y = rHeight * row - 1; - g.drawLine(clip.x, y, clip.width - 1, y); - } - } - - if (getShowVerticalLines()) { - g.setColor(getGridColor()); - - TableColumnModel model = getColumnModel(); - int x = -1; - for (int col = 0, n = model.getColumnCount(); col < n; col++) { - x += model.getColumn(col).getWidth(); - - if (x >= clip.x) { - if (x > clip.x + clip.width) { - break; - } - g.drawLine(x, clip.y, x, clip.y + clip.height - 1); - } - } - } - - getUI().paint(g, this); - } - - // - // JTable methods - // - - @Override - public Dimension getPreferredScrollableViewportSize() { - if (visibleRowCount < 0) { - return super.getPreferredScrollableViewportSize(); - } - - Insets insets = getInsets(); - Dimension d = getPreferredSize(); - d.height = visibleRowCount * getRowHeight() + insets.top + - insets.bottom; - - return d; - } - - @Override - public Object getValueAt(int vRow, int vCol) { - if (valueCacheSize > 0) { - CachedRow row = null; - ListIterator> rowIter = valueCache.listIterator(); - - while (rowIter.hasNext()) { - CachedRow r = rowIter.next(); - - if (r.row == vRow) { - row = r; - break; - } - - if (r.row > vRow) { - rowIter.previous(); - break; - } - } - - if (row == null) { - row = new CachedRow(vRow); - - rowIter.add(row); - - if (valueCache.size() > valueCacheSize) { - // Trim cached row farthest from the new one - int index = rowIter.previousIndex(); - int lastIndex = valueCache.size() - 1; - valueCache.remove(lastIndex - index > index - 0 ? - lastIndex : 0); - } - } - - CachedColumn col = null; - ListIterator> colIter = - row.cols.listIterator(); - - while (colIter.hasNext()) { - CachedColumn c = colIter.next(); - - if (c.col == vCol) { - col = c; - break; - } - - if (c.col > vCol) { - colIter.previous(); - break; - } - } - - if (col == null) { - Object value = super.getValueAt(vRow, vCol); - col = new CachedColumn(vCol, value); - colIter.add(col); - } - - return col.value; - } - - return super.getValueAt(vRow, vCol); - } - - @Override - public Component prepareRenderer(TableCellRenderer renderer, int row, - int col) { - - Component comp = super.prepareRenderer(renderer, row, col); - if (comp instanceof JComponent) { - ((JComponent)comp).setOpaque(isCellSelected(row, col)); - } - - return comp; - } - - @Override - public void setColumnModel(TableColumnModel columnModel) { - TableColumnModel old = super.getColumnModel(); - if (old != columnModel) { - if (columnListener == null) { - // Create this here since object members haven't yet been - // initialized if this method is being called from a superclass - // constructor - columnListener = new TableColumnModelListener() { - @Override - public void columnAdded(TableColumnModelEvent e) { - fitColumnsIfRequested(); - } - - @Override - public void columnMarginChanged(ChangeEvent e) { - } - - @Override - public void columnMoved(TableColumnModelEvent e) { - } - - @Override - public void columnRemoved(TableColumnModelEvent e) { - deleteUnusedColumnsFromWidthCache(); - } - - @Override - public void columnSelectionChanged(ListSelectionEvent e) { - } - }; - } - - if (old != null) { - old.removeColumnModelListener(columnListener); - } - - if (columnModel != null) { - columnModel.addColumnModelListener(columnListener); - } - - super.setColumnModel(columnModel); - - clearWidthCache(); - fitColumnsIfRequested(); - } - } - - @Override - public void setTableHeader(JTableHeader header) { - JTableHeader old = super.getTableHeader(); - if (old != header) { - super.setTableHeader(header); - - clearWidthCache(); - fitColumnsIfRequested(); - } - } - - @Override - public void setModel(TableModel model) { - TableModel old = super.getModel(); - if (old != model) { - if (modelListener == null) { - // Create this here since object members haven't yet been - // initialized if this method is being called from a superclass - // constructor - modelListener = new TableModelListener() { - @Override - public void tableChanged(TableModelEvent e) { - valueCache.clear(); - updateWidthCache(e); - } - }; - } - - if (old != null) { - old.removeTableModelListener(modelListener); - } - - if (model != null) { - model.addTableModelListener(modelListener); - } - - super.setModel(model); - - clearWidthCache(); - fitColumnsIfRequested(); - } - } - - @Override - public void setRowSorter(RowSorter sorter) { - RowSorter oldSorter = getRowSorter(); - if (sorter != oldSorter) { - if (sorterListener == null) { - // Create this here since object members haven't yet been - // initialized if this method is being called from a superclass - // constructor - sorterListener = new RowSorterListener() { - @Override - public void sorterChanged(RowSorterEvent e) { - valueCache.clear(); - - switch (e.getType()) { - case SORT_ORDER_CHANGED: - break; - - default: - case SORTED: - fitColumnsIfRequested(); - } - } - }; - - if (oldSorter != null) { - oldSorter.removeRowSorterListener(sorterListener); - } - - if (sorter != null) { - sorter.addRowSorterListener(sorterListener); - } - } - - super.setRowSorter(sorter); - fitColumnsIfRequested(); - } - } - - // - // ExtTable methods - // - - /** - * Calculates and sets the preferred widths for each {@code TableColumn} in - * the {@code TableColumnModel}. - *

- * This operation has a performance cost that increases with the number of - * rows or columns in the table, since each cell must be examined to - * determine its column's preferred width. However, this should be a - * one-time cost, since these values can be cached for subsequent or {@link - * #setAutoFitColumns automatic} calls to this routine. - * - * @param colWidthPolicy - * how to automatically fit columns - * - * @param useCache - * {@code true} to cache and reuse the values, {@code false} - * if this is a one-time operation - * - * @see #setAutoFitColumns - */ - public void fitColumns(ColumnWidthPolicy colWidthPolicy, boolean useCache) { - if (colWidthPolicy == null) { - return; - } - - synchronized (widthCache) { - TableModel model = getModel(); - TableColumnModel cModel = getColumnModel(); - int cMargin = getColumnModel().getColumnMargin(); - int nvRows = getRowCount(); - int maxColWidth = 0; - int n = getColumnCount(); - - for (int vCol = 0; vCol < n; vCol++) { - int mCol = convertColumnIndexToModel(vCol); - TableColumn column = cModel.getColumn(vCol); - - TableCellRenderer renderer = column.getCellRenderer(); - if (renderer == null) { - Class cClass = model.getColumnClass(mCol); - renderer = getDefaultRenderer(cClass); - } - - ArrayList colWidthCache = null; - - if (useCache) { - colWidthCache = widthCache.get(column); - if (colWidthCache == null) { - colWidthCache = new ArrayList(0); - colWidthCache.ensureCapacity(nvRows); - for (int i = 0; i < nvRows; i++) { - colWidthCache.add(-1); - } - widthCache.put(column, colWidthCache); - } - } - - int width = 0; - - for (int vRow = 0; vRow < nvRows; vRow++) { - int mRow = convertRowIndexToModel(vRow); - int cellWidth = 0; - try { - cellWidth = colWidthCache == null ? - -1 : colWidthCache.get(mRow); - } catch (IndexOutOfBoundsException ignore) { - // This method has probably been called (perhaps by the - // RowSorter) before insertWidthCache could react to a - // tableChange in the TableModel. That same change will - // ultimately make its way to our own modelListener, and - // this method will be called again (after - // insertWidthCache has been run) so we can abort now. - return; - } - - if (cellWidth == -1) { - Object value = getValueAt(vRow, vCol); - - Component comp = renderer.getTableCellRendererComponent( - this, value, true, true, vRow, vCol); - - cellWidth = comp.getPreferredSize().width; - - if (colWidthCache != null) { - colWidthCache.set(mRow, cellWidth); - } - } - - width = Math.max(width, cellWidth); - } - - // Account for header - renderer = column.getHeaderRenderer(); - if (renderer == null) { - JTableHeader header = getTableHeader(); - if (header != null) { - renderer = header.getDefaultRenderer(); - } - } - - if (renderer != null) { - Object value = column.getHeaderValue(); - - Component comp = renderer.getTableCellRendererComponent( - this, value, true, true, -1, vCol); - - int cellWidth = comp.getPreferredSize().width; - width = Math.max(width, cellWidth); - } - - width += cMargin; - - maxColWidth = Math.max(maxColWidth, width); - setPreferredWidth(column, width); - } - - if (colWidthPolicy == ColumnWidthPolicy.EQUAL) { - for (int vCol = 0; vCol < n; vCol++) { - int mCol = convertColumnIndexToModel(vCol); - TableColumn column = cModel.getColumn(vCol); - setPreferredWidth(column, maxColWidth); - } - } - } - } - - /** - * Indicates whether {@link #fitColumns} is called automatically whenever - * the table is changed in a significant way. - * - * @return how to automatically fit columns, or {@code null} to not - * automatically call {@link #fitColumns} - */ - public ColumnWidthPolicy getAutoFitColumns() { - return colWidthPolicy; - } - - /** - * Gets the number of rows in the cache of cell values. A higher value may - * speed up rendering of the table if the table model must do expensive - * calculations to determine the value in each cell. - *

- * The default value is 0, effectively disabling the cache. - */ - public int getCacheSize() { - return valueCacheSize; - } - - public Color getStripeColor() { - return stripeColor; - } - - /** - * Gets the number of rows used to calculate the preferred size of an - * enclosing viewport. - * - * @return the number of rows to show in an enclosing viewport, or -1 - * to rely on the default calculation for preferred viewport - * size - */ - public int getVisibleRowCount() { - return visibleRowCount; - } - - /** - * Indicates whether this table has a non-{@code null} {@link - * #getStripeColor stripe color}. - */ - public boolean isStriped() { - return getStripeColor() != null; - } - - /** - * Sets whether {@link #fitColumns} is called automatically whenever the - * table is changed in a significant way. - * - * @param colWidthPolicy - * how to automatically fit columns, or {@code null} to not - * automatically call {@link #fitColumns} - */ - public void setAutoFitColumns(ColumnWidthPolicy colWidthPolicy) { - if (this.colWidthPolicy != colWidthPolicy) { - this.colWidthPolicy = colWidthPolicy; - fitColumnsIfRequested(); - } - } - - /** - * Sets the number of rows in the cache of cell values. See {@link - * #getCacheSize}. - */ - public void setCacheSize(int valueCacheSize) { - if (this.valueCacheSize != valueCacheSize) { - if (this.valueCacheSize > valueCacheSize) { - try { - // Trim if necessary - ListIterator> rowIter = - valueCache.listIterator(valueCacheSize); - - while (rowIter.hasNext()) { - rowIter.next(); - rowIter.remove(); - } - } catch (IndexOutOfBoundsException ignore) { - } - } - - this.valueCacheSize = valueCacheSize; - } - } - - public void setStripeColor(Color stripeColor) { - if (this.stripeColor != stripeColor) { - this.stripeColor = stripeColor; - repaint(); - } - } - - /** - * Sets the number of rows used to calculate the preferred size of an - * enclosing viewport. - * - * @param visibleRowCount - * the number of rows to show in an enclosing viewport, or -1 - * to rely on the default calculation for preferred viewport - * size - */ - public void setVisibleRowCount(int visibleRowCount) { - this.visibleRowCount = visibleRowCount; - } - - // - // Private methods - // - - private void init() { - setTableHeader(new ExtTableHeader(getColumnModel())); - setFillsViewportHeight(true); - - addMouseListener( - new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - int vRow = rowAtPoint(e.getPoint()); - if (vRow == -1) { - clearSelection(); - } - } - }); - - setShowHorizontalLines(false); - setShowVerticalLines(true); - - // Accommodate a 16-pixel icon + a 1-pixel focus border - setRowHeight(18); - } - - private void clearWidthCache() { - if (widthCache != null) { - synchronized (widthCache) { - widthCache.clear(); - } - } - } - - private void deleteUnusedColumnsFromWidthCache() { - TableColumnModel cModel = getColumnModel(); - synchronized (widthCache) { - CACHED: for (Iterator i = - widthCache.keySet().iterator(); i.hasNext();) { - - TableColumn column = i.next(); - for (int j = 0, n = cModel.getColumnCount(); j < n; j++) { - if (cModel.getColumn(j) == column) { - continue CACHED; - } - } - i.remove(); - } - } - } - - private void deleteWidthCache(int fmRow, int lmRow) { - synchronized (widthCache) { - for (TableColumn column : widthCache.keySet()) { - ArrayList colWidthCache = widthCache.get(column); - for (int mRow = lmRow; mRow >= fmRow; mRow--) { - colWidthCache.remove(mRow); - } - } - } - } - - private void fitColumnsIfRequested() { - if (colWidthPolicy != null) { - fitColumns(colWidthPolicy, true); - } - } - - private void insertWidthCache(int fmRow, int lmRow) { - synchronized (widthCache) { - for (TableColumn column : widthCache.keySet()) { - ArrayList colWidthCache = widthCache.get(column); - for (int mRow = fmRow; mRow <= lmRow; mRow++) { - colWidthCache.add(mRow, -1); - } - } - } - } - - private void resetWidthCache(int fmRow, int lmRow, int fmCol, int lmCol) { - synchronized (widthCache) { - TableColumnModel cModel = getColumnModel(); - - for (int mCol = fmCol; mCol <= lmCol; mCol++) { - int vCol = convertColumnIndexToView(mCol); - TableColumn column = cModel.getColumn(vCol); - - ArrayList colWidthCache = widthCache.get(column); - if (colWidthCache != null) { - for (int mRow = fmRow; mRow <= lmRow; mRow++) { - try { - colWidthCache.set(mRow, -1); - } catch (IndexOutOfBoundsException ignore) { - } - } - } - } - } - } - - private void setPreferredWidth(TableColumn column, int width) { - column.setPreferredWidth(width); - column.setMinWidth(Math.min(column.getMinWidth(), width)); - } - - private void updateWidthCache(TableModelEvent e) { - synchronized (widthCache) { - int fmRow = e.getFirstRow(); - int lmRow = e.getLastRow(); - - int nRows = getModel().getRowCount(); - if (lmRow >= nRows) { - lmRow = nRows - 1; - } - - switch (e.getType()) { - case TableModelEvent.UPDATE: - int fmCol = e.getColumn(); - int lmCol = fmCol; - - if (fmCol == TableModelEvent.ALL_COLUMNS) { - fmCol = 0; - lmCol = getColumnModel().getColumnCount() - 1; - } - - resetWidthCache(fmRow, lmRow, fmCol, lmCol); - break; - - - case TableModelEvent.DELETE: - deleteWidthCache(fmRow, lmRow); - break; - - case TableModelEvent.INSERT: - insertWidthCache(fmRow, lmRow); - break; - } - - fitColumnsIfRequested(); - } - } - - // - // Static methods - // - - // XXX Unit test -- remove - public static void main(String[] args) { - JFrame f = new JFrame(); - - JPanel c = (JPanel)f.getContentPane(); - c.setLayout(new BorderLayout()); - c.setBorder(GUIUtil.getEmptyBorder()); - - Object[][] data = { - new Object[] {"0 zero", "0 zero", "blah"}, - new Object[] {"1 one", "1 one", "blah"}, - new Object[] {"2 two", "2 two", "blah"}, - new Object[] {"3 three", "3 three", "blah"}, - new Object[] {"4 four", "4 four", "blah"}, - new Object[] {"5 five", "5 five", "blah"}, - }; - - final DefaultTableModel model = new DefaultTableModel( - data, new String[] {"Column 0", "Column 1", "Column 2"}); - - final ExtTable t = new ExtTable(model); - t.setAutoFitColumns(ColumnWidthPolicy.PREFERRED); -// t.setOpaque(false); -// t.setBackground(Color.green); -// t.setStripeColor(null); -// t.setShowGrid(true); - - t.addMouseListener( - new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.getButton() == 3) { - Point p = e.getPoint(); - int vCol = t.columnAtPoint(p); - int mCol = t.convertColumnIndexToModel(vCol); - - Object[] row = {"new", "new", "new"}; - row[mCol] = "loooooooooooooooooooooooooong"; - model.addRow(row); - } else - - if (e.getButton() == 2) { - Point p = e.getPoint(); - int vRow = t.rowAtPoint(p); - int mRow = t.convertRowIndexToModel(vRow); - model.removeRow(mRow); - } - } - }); - - JScrollPane scroll = new ExtScrollPane(t); - c.add(scroll, BorderLayout.CENTER); - - JButton b = new JButton("Remove column"); - b.addActionListener( - new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - TableColumn column = t.getColumnModel().getColumn(1); - System.out.printf("Removing column: %s\n", - column.getIdentifier()); - t.removeColumn(column); - } - }); - c.add(b, BorderLayout.SOUTH); - - f.pack(); - f.setVisible(true); - } -}