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