| /* BasicTableHeaderUI.java -- |
| Copyright (C) 2004 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package javax.swing.plaf.basic; |
| |
| import gnu.classpath.NotImplementedException; |
| |
| import java.awt.Component; |
| import java.awt.Cursor; |
| import java.awt.Dimension; |
| import java.awt.Graphics; |
| import java.awt.Rectangle; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.awt.event.MouseEvent; |
| |
| import javax.swing.CellRendererPane; |
| import javax.swing.JComponent; |
| import javax.swing.LookAndFeel; |
| import javax.swing.Timer; |
| import javax.swing.UIManager; |
| import javax.swing.border.Border; |
| import javax.swing.event.MouseInputListener; |
| import javax.swing.plaf.ComponentUI; |
| import javax.swing.plaf.TableHeaderUI; |
| import javax.swing.table.JTableHeader; |
| import javax.swing.table.TableCellRenderer; |
| import javax.swing.table.TableColumn; |
| import javax.swing.table.TableColumnModel; |
| |
| /** |
| * Basic pluggable look and feel interface for JTableHeader. |
| */ |
| public class BasicTableHeaderUI extends TableHeaderUI |
| { |
| /** |
| * The width of the space (in both direction) around the column boundary, |
| * where mouse cursor changes shape into "resize" |
| */ |
| static int COLUMN_BOUNDARY_TOLERANCE = 3; |
| |
| public static ComponentUI createUI(JComponent h) |
| { |
| return new BasicTableHeaderUI(); |
| } |
| |
| /** |
| * The table header that is using this interface. |
| */ |
| protected JTableHeader header; |
| |
| /** |
| * The mouse input listener, responsible for mouse manipulations with |
| * the table header. |
| */ |
| protected MouseInputListener mouseInputListener; |
| |
| /** |
| * Paint the header cell. |
| */ |
| protected CellRendererPane rendererPane; |
| |
| /** |
| * The header cell border. |
| */ |
| private Border cellBorder; |
| |
| /** |
| * Original mouse cursor prior to resizing. |
| */ |
| private Cursor originalCursor; |
| |
| /** |
| * If not null, one of the columns is currently being dragged. |
| */ |
| Rectangle draggingHeaderRect; |
| |
| /** |
| * Handles column movement and rearrangement by mouse. The same instance works |
| * both as mouse listener and the mouse motion listner. |
| */ |
| public class MouseInputHandler |
| implements MouseInputListener |
| { |
| /** |
| * If true, the cursor is being already shown in the alternative "resize" |
| * shape. |
| */ |
| boolean showingResizeCursor; |
| |
| /** |
| * The position, from where the cursor is dragged during resizing. Double |
| * purpose field (absolute value during resizing and relative offset during |
| * column dragging). |
| */ |
| int draggingFrom = - 1; |
| |
| /** |
| * The number of the column being dragged. |
| */ |
| int draggingColumnNumber; |
| |
| /** |
| * The previous preferred width of the column. |
| */ |
| int prevPrefWidth = - 1; |
| |
| /** |
| * The timer to coalesce column resizing events. |
| */ |
| Timer timer; |
| |
| /** |
| * Returns without action, part of the MouseInputListener interface. |
| */ |
| public void mouseClicked(MouseEvent e) |
| { |
| // Nothing to do. |
| } |
| |
| /** |
| * If being in the resizing mode, handle resizing. |
| */ |
| public void mouseDragged(MouseEvent e) |
| { |
| TableColumn resizeIt = header.getResizingColumn(); |
| if (resizeIt != null && header.getResizingAllowed()) |
| { |
| // The timer is intialised on demand. |
| if (timer == null) |
| { |
| // The purpose of timer is to coalesce events. If the queue |
| // is free, the repaint event is fired immediately. |
| timer = new Timer(1, new ActionListener() |
| { |
| public void actionPerformed(ActionEvent e) |
| { |
| header.getTable().doLayout(); |
| } |
| }); |
| timer.setRepeats(false); |
| timer.setCoalesce(true); |
| } |
| resizeIt.setPreferredWidth(prevPrefWidth + e.getX() - draggingFrom); |
| timer.restart(); |
| } |
| else if (draggingHeaderRect != null && header.getReorderingAllowed()) |
| { |
| draggingHeaderRect.x = e.getX() + draggingFrom; |
| header.repaint(); |
| } |
| } |
| |
| /** |
| * Returns without action, part of the MouseInputListener interface. |
| */ |
| public void mouseEntered(MouseEvent e) |
| { |
| // Nothing to do. |
| } |
| |
| /** |
| * Reset drag information of the column resizing. |
| */ |
| public void mouseExited(MouseEvent e) |
| { |
| // Nothing to do. |
| } |
| |
| /** |
| * Change the mouse cursor if the mouse if above the column boundary. |
| */ |
| public void mouseMoved(MouseEvent e) |
| { |
| // When dragging, the functionality is handled by the mouseDragged. |
| if (e.getButton() == 0 && header.getResizingAllowed()) |
| { |
| TableColumnModel model = header.getColumnModel(); |
| int n = model.getColumnCount(); |
| if (n < 2) |
| // It must be at least two columns to have at least one boundary. |
| // Otherwise, nothing to do. |
| return; |
| |
| boolean onBoundary = false; |
| |
| int x = e.getX(); |
| int a = x - COLUMN_BOUNDARY_TOLERANCE; |
| int b = x + COLUMN_BOUNDARY_TOLERANCE; |
| |
| int p = 0; |
| |
| Scan: for (int i = 0; i < n - 1; i++) |
| { |
| p += model.getColumn(i).getWidth(); |
| |
| if (p >= a && p <= b) |
| { |
| TableColumn column = model.getColumn(i); |
| onBoundary = true; |
| |
| draggingFrom = x; |
| prevPrefWidth = column.getWidth(); |
| header.setResizingColumn(column); |
| break Scan; |
| } |
| } |
| |
| if (onBoundary != showingResizeCursor) |
| { |
| // Change the cursor shape, if needed. |
| if (onBoundary) |
| { |
| |
| originalCursor = header.getCursor(); |
| if (p < x) |
| header.setCursor(Cursor.getPredefinedCursor( |
| Cursor.W_RESIZE_CURSOR)); |
| else |
| header.setCursor(Cursor.getPredefinedCursor( |
| Cursor.E_RESIZE_CURSOR)); |
| } |
| else |
| { |
| header.setCursor(originalCursor); |
| header.setResizingColumn(null); |
| } |
| |
| showingResizeCursor = onBoundary; |
| } |
| } |
| } |
| |
| /** |
| * Starts the dragging/resizing procedure. |
| */ |
| public void mousePressed(MouseEvent e) |
| { |
| if (header.getResizingAllowed()) |
| { |
| TableColumn resizingColumn = header.getResizingColumn(); |
| if (resizingColumn != null) |
| { |
| resizingColumn.setPreferredWidth(resizingColumn.getWidth()); |
| return; |
| } |
| } |
| |
| if (header.getReorderingAllowed()) |
| { |
| TableColumnModel model = header.getColumnModel(); |
| int n = model.getColumnCount(); |
| if (n < 2) |
| // It must be at least two columns to change the column location. |
| return; |
| |
| boolean onBoundary = false; |
| |
| int x = e.getX(); |
| int p = 0; |
| int col = - 1; |
| |
| Scan: for (int i = 0; i < n; i++) |
| { |
| p += model.getColumn(i).getWidth(); |
| if (p > x) |
| { |
| col = i; |
| break Scan; |
| } |
| } |
| if (col < 0) |
| return; |
| |
| TableColumn dragIt = model.getColumn(col); |
| header.setDraggedColumn(dragIt); |
| |
| draggingFrom = (p - dragIt.getWidth()) - x; |
| draggingHeaderRect = new Rectangle(header.getHeaderRect(col)); |
| draggingColumnNumber = col; |
| } |
| } |
| |
| /** |
| * Set all column preferred width to the current width to prevend abrupt |
| * width changes during the next resize. |
| */ |
| public void mouseReleased(MouseEvent e) |
| { |
| if (header.getResizingColumn() != null && header.getResizingAllowed()) |
| endResizing(); |
| if (header.getDraggedColumn() != null && header.getReorderingAllowed()) |
| endDragging(e); |
| } |
| |
| /** |
| * Stop resizing session. |
| */ |
| void endResizing() |
| { |
| TableColumnModel model = header.getColumnModel(); |
| int n = model.getColumnCount(); |
| if (n > 2) |
| { |
| TableColumn c; |
| for (int i = 0; i < n; i++) |
| { |
| c = model.getColumn(i); |
| c.setPreferredWidth(c.getWidth()); |
| } |
| } |
| header.setResizingColumn(null); |
| showingResizeCursor = false; |
| if (timer != null) |
| timer.stop(); |
| header.setCursor(originalCursor); |
| } |
| |
| /** |
| * Stop the dragging session. |
| * |
| * @param e the "mouse release" mouse event, needed to determing the final |
| * location for the dragged column. |
| */ |
| void endDragging(MouseEvent e) |
| { |
| header.setDraggedColumn(null); |
| draggingHeaderRect = null; |
| |
| TableColumnModel model = header.getColumnModel(); |
| |
| // Find where have we dragged the column. |
| int x = e.getX(); |
| int p = 0; |
| |
| int col = model.getColumnCount() - 1; |
| int n = model.getColumnCount(); |
| |
| // This loop does not find the column if the mouse if out of the |
| // right boundary of the table header. Then we make this column the |
| // rightmost column. |
| Scan: for (int i = 0; i < n; i++) |
| { |
| p += model.getColumn(i).getWidth(); |
| if (p > x) |
| { |
| col = i; |
| break Scan; |
| } |
| } |
| |
| header.getTable().moveColumn(draggingColumnNumber, col); |
| } |
| } |
| |
| /** |
| * Create and return the mouse input listener. |
| * |
| * @return the mouse listener ({@link MouseInputHandler}, if not overridden. |
| */ |
| protected MouseInputListener createMouseInputListener() |
| { |
| return new MouseInputHandler(); |
| } |
| |
| /** |
| * Construct a new BasicTableHeaderUI, create mouse listeners. |
| */ |
| public BasicTableHeaderUI() |
| { |
| mouseInputListener = createMouseInputListener(); |
| } |
| |
| protected void installDefaults() |
| { |
| LookAndFeel.installColorsAndFont(header, "TableHeader.background", |
| "TableHeader.foreground", |
| "TableHeader.font"); |
| cellBorder = UIManager.getBorder("TableHeader.cellBorder"); |
| } |
| |
| protected void installKeyboardActions() |
| throws NotImplementedException |
| { |
| // TODO: Implement this properly. |
| } |
| |
| /** |
| * Add the mouse listener and the mouse motion listener to the table |
| * header. The listeners support table column resizing and rearrangement |
| * by mouse. |
| */ |
| protected void installListeners() |
| { |
| header.addMouseListener(mouseInputListener); |
| header.addMouseMotionListener(mouseInputListener); |
| } |
| |
| public void installUI(JComponent c) |
| { |
| header = (JTableHeader) c; |
| rendererPane = new CellRendererPane(); |
| installDefaults(); |
| installKeyboardActions(); |
| installListeners(); |
| } |
| |
| protected void uninstallDefaults() |
| { |
| header.setBackground(null); |
| header.setForeground(null); |
| header.setFont(null); |
| } |
| |
| protected void uninstallKeyboardActions() |
| throws NotImplementedException |
| { |
| // TODO: Implement this properly. |
| } |
| |
| /** |
| * Remove the previously installed listeners. |
| */ |
| protected void uninstallListeners() |
| { |
| header.removeMouseListener(mouseInputListener); |
| header.removeMouseMotionListener(mouseInputListener); |
| } |
| |
| public void uninstallUI(JComponent c) |
| { |
| uninstallListeners(); |
| uninstallKeyboardActions(); |
| uninstallDefaults(); |
| } |
| |
| /** |
| * Repaint the table header. |
| */ |
| public void paint(Graphics gfx, JComponent c) |
| { |
| TableColumnModel cmod = header.getColumnModel(); |
| int ncols = cmod.getColumnCount(); |
| if (ncols == 0) |
| return; |
| |
| Rectangle clip = gfx.getClipBounds(); |
| TableCellRenderer defaultRend = header.getDefaultRenderer(); |
| |
| for (int i = 0; i < ncols; ++i) |
| { |
| Rectangle bounds = header.getHeaderRect(i); |
| if (bounds.intersects(clip)) |
| { |
| Rectangle oldClip = gfx.getClipBounds(); |
| TableColumn col = cmod.getColumn(i); |
| TableCellRenderer rend = col.getHeaderRenderer(); |
| if (rend == null) |
| rend = defaultRend; |
| Object val = col.getHeaderValue(); |
| Component comp = rend.getTableCellRendererComponent(header.getTable(), |
| val, |
| false, // isSelected |
| false, // isFocused |
| -1, i); |
| // FIXME: The following settings should be performed in |
| // rend.getTableCellRendererComponent(). |
| comp.setFont(header.getFont()); |
| comp.setBackground(header.getBackground()); |
| comp.setForeground(header.getForeground()); |
| if (comp instanceof JComponent) |
| ((JComponent) comp).setBorder(cellBorder); |
| rendererPane.paintComponent(gfx, comp, header, bounds.x, bounds.y, |
| bounds.width, bounds.height); |
| } |
| } |
| |
| // This displays a running rectangle that is much simplier than the total |
| // animation, as it is seen in Sun's application. |
| // TODO animate the collumn dragging like in Sun's jre. |
| if (draggingHeaderRect != null) |
| { |
| gfx.setColor(header.getForeground()); |
| gfx.drawRect(draggingHeaderRect.x, draggingHeaderRect.y + 2, |
| draggingHeaderRect.width - 1, draggingHeaderRect.height - 6); |
| } |
| } |
| |
| /** |
| * Get the preferred header size. |
| * |
| * @param ignored unused |
| * |
| * @return the preferred size of the associated header. |
| */ |
| public Dimension getPreferredSize(JComponent ignored) |
| { |
| TableColumnModel cmod = header.getColumnModel(); |
| TableCellRenderer defaultRend = header.getDefaultRenderer(); |
| int ncols = cmod.getColumnCount(); |
| Dimension ret = new Dimension(0, 0); |
| int spacing = 0; |
| |
| if (header.getTable() != null |
| && header.getTable().getIntercellSpacing() != null) |
| spacing = header.getTable().getIntercellSpacing().width; |
| |
| for (int i = 0; i < ncols; ++i) |
| { |
| TableColumn col = cmod.getColumn(i); |
| TableCellRenderer rend = col.getHeaderRenderer(); |
| if (rend == null) |
| rend = defaultRend; |
| Object val = col.getHeaderValue(); |
| Component comp = rend.getTableCellRendererComponent(header.getTable(), |
| val, |
| false, // isSelected |
| false, // isFocused |
| -1, i); |
| comp.setFont(header.getFont()); |
| comp.setBackground(header.getBackground()); |
| comp.setForeground(header.getForeground()); |
| if (comp instanceof JComponent) |
| ((JComponent) comp).setBorder(cellBorder); |
| |
| Dimension d = comp.getPreferredSize(); |
| ret.width += spacing; |
| ret.height = Math.max(d.height, ret.height); |
| } |
| ret.width = cmod.getTotalColumnWidth(); |
| return ret; |
| } |
| |
| |
| } |