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 */ 021package csheets.ui.grid; 022 023import java.awt.Component; 024import java.awt.Cursor; 025import java.awt.Dimension; 026import java.awt.Graphics; 027import java.awt.Point; 028import java.awt.Rectangle; 029import java.awt.event.MouseEvent; 030import java.util.Arrays; 031 032import javax.swing.CellRendererPane; 033import javax.swing.JComponent; 034import javax.swing.JTable; 035import javax.swing.SwingConstants; 036import javax.swing.event.MouseInputAdapter; 037import javax.swing.event.MouseInputListener; 038import 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") 048public 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}