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 }