001    /*
002     * Copyright (c) 2005 Einar Pehrson <einar@pehrson.nu>.
003     *
004     * This file is part of
005     * CleanSheets - a spreadsheet application for the Java platform.
006     *
007     * CleanSheets is free software; you can redistribute it and/or modify
008     * it under the terms of the GNU General Public License as published by
009     * the Free Software Foundation; either version 2 of the License, or
010     * (at your option) any later version.
011     *
012     * CleanSheets is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015     * GNU General Public License for more details.
016     *
017     * You should have received a copy of the GNU General Public License
018     * along with CleanSheets; if not, write to the Free Software
019     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
020     */
021    package csheets.ui.grid;
022    
023    import java.awt.Component;
024    import java.awt.Cursor;
025    import java.awt.Dimension;
026    import java.awt.Graphics;
027    import java.awt.Point;
028    import java.awt.Rectangle;
029    import java.awt.event.MouseEvent;
030    import java.util.Arrays;
031    
032    import javax.swing.CellRendererPane;
033    import javax.swing.JComponent;
034    import javax.swing.JTable;
035    import javax.swing.SwingConstants;
036    import javax.swing.event.MouseInputAdapter;
037    import javax.swing.event.MouseInputListener;
038    import javax.swing.table.TableCellRenderer;
039    
040    
041    /**
042     * The row header for spreadsheet tables. This component emulates the
043     * behaviour of <code>javax.swing.plaf.basic.BasicTableHeaderUI</code>, but
044     * paints the header vertically in stead of horizontally.
045     * @author Einar Pehrson
046     */
047    @SuppressWarnings("serial")
048    public class RowHeader extends JComponent {
049    
050            /** The table to which the row header belongs */
051            private JTable table;
052    
053            /** The header's renderer pane */
054            private CellRendererPane rendererPane = new CellRendererPane();
055    
056            /** The row header renderer*/
057            private TableCellRenderer renderer
058                    = new HeaderRenderer(SwingConstants.VERTICAL);
059    
060            /** The width of the row header */
061            private int width = 30;
062    
063            /** The minimum height of rows */
064            private int minRowHeight = 5;
065    
066            /** The margin around the packed rows of the header. */
067        private int rowMargin = 1;
068    
069            /** The index of the row being resized, or -1 */
070            private int resizingRow = -1;
071    
072            /**
073             * Creates a new row header for the given table.
074             * @param table the table to which the row header belongs
075             */
076            public RowHeader(JTable table) {
077                    this.table = table;
078                    add(rendererPane);
079                    setPreferredSize(new Dimension(width, table.getRowCount() * table.getRowHeight()));
080                    MouseInputListener rowResizer = new RowResizer();
081                    addMouseListener(rowResizer);
082                    addMouseMotionListener(rowResizer);
083            }
084    
085            public void paint(Graphics g) {
086                    // Calculates visible area
087                    Rectangle bounds = g.getClipBounds(); 
088                    Point top = bounds.getLocation();
089                    Point bottom = new Point(bounds.x, bounds.y + bounds.height - 1);
090    
091                    // Finds rows to paint
092                    int minRow = table.rowAtPoint(top);
093                    int maxRow = table.rowAtPoint(bottom);
094                    if (minRow == -1)
095                            minRow =  0;
096                    if (maxRow == -1)
097                            maxRow = table.getRowCount()-1;  
098    
099                    // Paints rows
100                    int y = table.getCellRect(minRow, 0, true).y;
101                    int[] selectedRows = table.getSelectedRows();
102                    for (int row = minRow; row <= maxRow; row++) {
103                            // Fetches component from renderer
104                            boolean selected = Arrays.binarySearch(selectedRows, row) >= 0;
105                            Component c = renderer.getTableCellRendererComponent(
106                                    table, null, selected, false, row, -1);
107    
108                            // Calculates coordinates and paints component
109                            int rowHeight = table.getRowHeight(row);
110                            rendererPane.paintComponent(g, c, this,
111                                    0, y, width, rowHeight, true);
112                            y += rowHeight;
113                    }
114            }
115    
116            /**
117             * Adjusts the height of the row at the given index to precisely fit all
118             * data being rendered.
119             * @param row the index of the row to auto-resize
120             */
121            public void autoResize(int row) {
122                    // Gets width of row header
123                    int height = renderer.getTableCellRendererComponent(table, null,
124                                    false, false, row, 0).getPreferredSize().height;
125                    
126                    // Gets maximum width of column data
127                    for (int column = 0; column < table.getColumnCount(); column++) {
128                            Component c = table.getCellRenderer(row, column)
129                                    .getTableCellRendererComponent(table, table.getValueAt
130                                            (row, column), false, false, row, column);
131                            height = Math.max(height, c.getPreferredSize().height);
132                    }
133    
134                    // Adds margin
135                    height += 2 * rowMargin;
136                    if (height > minRowHeight) {
137                            table.setRowHeight(resizingRow, height);
138                    } else
139                            table.setRowHeight(minRowHeight);
140                    repaint();
141            }
142    
143            /**
144             * A mouse input listener that enables resizing of rows
145             */
146            protected class RowResizer extends MouseInputAdapter {
147    
148                    /** The normal cursor */
149                    private final Cursor NORMAL_CURSOR = getCursor();
150    
151                    /** The cursor to display when resizing a row */
152                    private final Cursor RESIZE_CURSOR
153                            = Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR);
154    
155                    /**
156                     * Selects the clicked row, unless resizing is intended.
157                     * @param e the event that was fired
158                     */
159                    public void mousePressed(MouseEvent e) {
160                            // Checks what was done
161                            resizingRow = getResizingRow(e.getPoint());
162                            if (resizingRow == -1) {
163                                    int pressedRow = table.rowAtPoint(e.getPoint());
164                                    int columns = table.getColumnCount();
165            
166                                    // Configures new selection
167                                    if (e.isShiftDown())
168                                            table.changeSelection(pressedRow, 0, false, true);
169                                    else if (e.isControlDown())
170                                            table.changeSelection(pressedRow, 0, true, false);
171                                    else {
172                                            table.changeSelection(pressedRow, columns, false, false);
173                                            table.changeSelection(pressedRow, 0, false, true);
174                                    }
175                                    repaint();
176                            }
177                    }
178    
179                    /**
180                     * Auto-resizes a column whose border was double-clicked.
181                     * @param e the event that was fired
182                     */
183                    public void mouseClicked(MouseEvent e) {
184                            if (e.getClickCount() == 2 && resizingRow != -1)
185                                    autoResize(resizingRow);
186                    }
187    
188                    /**
189                     * Sets the appropriate cursor depending on whether the mouse is on
190                     * a row that can be resized.
191                     * @param e the event that was fired
192                     */
193                    public void mouseMoved(MouseEvent e) { 
194                            setCursor(getResizingRow(e.getPoint()) == -1
195                                    ? NORMAL_CURSOR : RESIZE_CURSOR);
196                    }
197    
198                    /**
199                     * Resizes the row that is dragged
200                     * @param e the event that was fired
201                     */
202                    public void mouseDragged(MouseEvent e) {
203                            if (resizingRow != -1) {
204                                    int rowHeight = e.getPoint().y
205                                            - table.getCellRect(resizingRow, 0, true).y;
206                                    if (rowHeight >= minRowHeight)
207                                            table.setRowHeight(resizingRow, rowHeight);
208                                    repaint();
209                            }
210                    }
211    
212                    /**
213                     * Retrieves the index of the row at the given point, if it can be
214                     * resized.
215                     * @param p the point to look at
216                     * @return the index of the row, or -1 if it can not be resized
217                     */
218                    private int getResizingRow(Point p) {
219                            // Fetches the row index, and stops if it is invalid
220                            int row = table.rowAtPoint(p);
221                            if (row == -1)
222                                    return row;
223    
224                            // Fetches the bounding rectangle of the header row
225                            Rectangle r = table.getCellRect(row, 0, true);
226                            r = new Rectangle(0, r.y, width, table.getRowHeight(row));
227                            r.grow(0, -2);
228    
229                            // Stops if the point is inside the header row
230                            if (r.contains(p))
231                                    return -1;
232    
233                            // If above the middle of the row, resize previous row
234                            if (p.y < (r.y + (r.height / 2)))
235                                    row--;
236                            return row;
237                    }
238            }
239    }