001    /*
002     * $Id: BetterJTable.java,v 1.3 2012/02/19 17:35:50 davep Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
007     * Space Science and Engineering Center (SSEC)
008     * University of Wisconsin - Madison
009     * 1225 W. Dayton Street, Madison, WI 53706, USA
010     * https://www.ssec.wisc.edu/mcidas
011     * 
012     * All Rights Reserved
013     * 
014     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015     * some McIDAS-V source code is based on IDV and VisAD source code.  
016     * 
017     * McIDAS-V is free software; you can redistribute it and/or modify
018     * it under the terms of the GNU Lesser Public License as published by
019     * the Free Software Foundation; either version 3 of the License, or
020     * (at your option) any later version.
021     * 
022     * McIDAS-V is distributed in the hope that it will be useful,
023     * but WITHOUT ANY WARRANTY; without even the implied warranty of
024     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025     * GNU Lesser Public License for more details.
026     * 
027     * You should have received a copy of the GNU Lesser Public License
028     * along with this program.  If not, see http://www.gnu.org/licenses.
029     */
030    package edu.wisc.ssec.mcidasv.ui;
031    
032    import java.awt.Color;
033    import java.awt.Component;
034    import java.awt.Dimension;
035    import java.awt.Graphics;
036    import java.beans.PropertyChangeEvent;
037    import java.beans.PropertyChangeListener;
038    
039    import javax.swing.BorderFactory;
040    import javax.swing.CellRendererPane;
041    import javax.swing.JComponent;
042    import javax.swing.JScrollPane;
043    import javax.swing.JTable;
044    import javax.swing.JViewport;
045    import javax.swing.table.JTableHeader;
046    import javax.swing.table.TableCellRenderer;
047    import javax.swing.table.TableColumn;
048    import javax.swing.table.TableModel;
049    
050    public class BetterJTable extends JTable {
051    
052        private static final Color EVEN_ROW_COLOR = new Color(241, 245, 250);
053        private static final Color TABLE_GRID_COLOR = new Color(0xd9d9d9);
054    
055        private static final CellRendererPane CELL_RENDER_PANE = new CellRendererPane();
056    
057        public BetterJTable() {
058            super();
059            init();
060        }
061        public BetterJTable(TableModel dm) {
062            super(dm);
063            init();
064        }
065    
066        private void init() {
067    //        setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
068            setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
069            setTableHeader(createTableHeader());
070            getTableHeader().setReorderingAllowed(false);
071            setOpaque(false);
072            setGridColor(TABLE_GRID_COLOR);
073            // turn off grid painting as we'll handle this manually in order to paint
074            // grid lines over the entire viewport.
075            setShowGrid(false);
076        }
077    
078        /**
079         * Creates a JTableHeader that paints the table header background to the right
080         * of the right-most column if neccesasry.
081         */
082        private JTableHeader createTableHeader() {
083            return new JTableHeader(getColumnModel()) {
084                @Override protected void paintComponent(Graphics g) {
085                    super.paintComponent(g);
086                    // if this JTableHeader is parented in a JViewport, then paint the
087                    // table header background to the right of the last column if
088                    // neccessary.
089                    JViewport viewport = (JViewport) table.getParent();
090                    if (viewport != null && table.getWidth() < viewport.getWidth()) {
091                        int x = table.getWidth();
092                        int width = viewport.getWidth() - table.getWidth();
093                        paintHeader(g, getTable(), x, width);
094                    }
095                }
096            };
097        }
098    
099        /**
100         * Paints the given JTable's table default header background at given
101         * x for the given width.
102         */
103        private static void paintHeader(Graphics g, JTable table, int x, int width) {
104            TableCellRenderer renderer = table.getTableHeader().getDefaultRenderer();
105            Component component = renderer.getTableCellRendererComponent(
106                    table, "", false, false, -1, 2);
107    
108            component.setBounds(0, 0, width, table.getTableHeader().getHeight());
109    
110            ((JComponent)component).setOpaque(false);
111            CELL_RENDER_PANE.paintComponent(g, component, null, x, 0,
112                    width, table.getTableHeader().getHeight(), true);
113        }
114    
115        @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
116            Component component = super.prepareRenderer(renderer, row, column);
117            // if the rendere is a JComponent and the given row isn't part of a
118            // selection, make the renderer non-opaque so that striped rows show
119            // through.
120            if (component instanceof JComponent) {
121                ((JComponent)component).setOpaque(getSelectionModel().isSelectedIndex(row));
122            }
123            return component;
124        }
125    
126        // Stripe painting Viewport. //////////////////////////////////////////////
127    
128        /**
129         * Creates a JViewport that draws a striped backgroud corresponding to the
130         * row positions of the given JTable.
131         */
132        public static class StripedViewport extends JViewport {
133    
134            private final JTable fTable;
135    
136            public StripedViewport(JTable table) {
137                fTable = table;
138                setOpaque(false);
139                initListeners();
140            }
141    
142            private void initListeners() {
143                // install a listener to cause the whole table to repaint when
144                // a column is resized. we do this because the extended grid
145                // lines may need to be repainted. this could be cleaned up,
146                // but for now, it works fine.
147                PropertyChangeListener listener = createTableColumnWidthListener();
148                for (int i = 0; i < fTable.getColumnModel().getColumnCount(); i++) {
149                    fTable.getColumnModel().getColumn(i).addPropertyChangeListener(listener);
150                }
151            }
152    
153            private PropertyChangeListener createTableColumnWidthListener() {
154                return new PropertyChangeListener() {
155                    public void propertyChange(PropertyChangeEvent evt) {
156                        repaint();
157                    }
158                };
159            }
160    
161            @Override protected void paintComponent(Graphics g) {
162                paintStripedBackground(g);
163                paintVerticalGridLines(g);
164                super.paintComponent(g);
165            }
166    
167            private void paintStripedBackground(Graphics g) {
168                // get the row index at the top of the clip bounds (the first row
169                // to paint).
170                int rowAtPoint = fTable.rowAtPoint(g.getClipBounds().getLocation());
171                // get the y coordinate of the first row to paint. if there are no
172                // rows in the table, start painting at the top of the supplied
173                // clipping bounds.
174                int topY = rowAtPoint < 0
175                        ? g.getClipBounds().y : fTable.getCellRect(rowAtPoint,0,true).y;
176    
177                // create a counter variable to hold the current row. if there are no
178                // rows in the table, start the counter at 0.
179                int currentRow = rowAtPoint < 0 ? 0 : rowAtPoint;
180                while (topY < g.getClipBounds().y + g.getClipBounds().height) {
181                    int bottomY = topY + fTable.getRowHeight();
182                    g.setColor(getRowColor(currentRow));
183                    g.fillRect(g.getClipBounds().x, topY, g.getClipBounds().width, bottomY);
184                    topY = bottomY;
185                    currentRow ++;
186                }
187            }
188    
189            private Color getRowColor(int row) {
190                return row % 2 == 0 ? EVEN_ROW_COLOR : getBackground();
191            }
192    
193            private void paintVerticalGridLines(Graphics g) {
194                // paint the column grid dividers for the non-existent rows.
195                int x = 0;
196                for (int i = 0; i < fTable.getColumnCount(); i++) {
197                    TableColumn column = fTable.getColumnModel().getColumn(i);
198                    // increase the x position by the width of the current column.
199                    x += column.getWidth();
200                    g.setColor(TABLE_GRID_COLOR);
201                    // draw the grid line (not sure what the -1 is for, but BasicTableUI
202                    // also does it.
203                    g.drawLine(x - 1, g.getClipBounds().y, x - 1, getHeight());
204                }
205            }
206        }
207    
208        public static JScrollPane createStripedJScrollPane(JTable table) {
209            JScrollPane scrollPane =  new JScrollPane(table);
210            scrollPane.setViewport(new StripedViewport(table));
211            scrollPane.getViewport().setView(table);
212            scrollPane.setBorder(BorderFactory.createEmptyBorder());
213    //        scrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER,
214    //                createCornerComponent(table));
215            return scrollPane;
216        }
217    
218        /**
219         * Creates a component that paints the header background for use in a
220         * JScrollPane corner.
221         */
222        private static JComponent createCornerComponent(final JTable table) {
223            return new JComponent() {
224                @Override
225                protected void paintComponent(Graphics g) {
226                    paintHeader(g, table, 0, getWidth());
227                }
228            };
229        }
230    }