1. /*
  2. * @(#)BasicTableHeaderUI.java 1.41 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import javax.swing.table.*;
  9. import javax.swing.*;
  10. import javax.swing.event.*;
  11. import java.util.Enumeration;
  12. import java.awt.event.*;
  13. import java.awt.*;
  14. import javax.swing.plaf.*;
  15. /**
  16. * BasicTableHeaderUI implementation
  17. *
  18. * @version 1.41 11/29/01
  19. * @author Philip Milne
  20. * @author Alan Chung
  21. */
  22. public class BasicTableHeaderUI extends TableHeaderUI {
  23. //
  24. // Instance Variables
  25. //
  26. /** The JTableHeader that is delegating the painting to this UI. */
  27. protected JTableHeader header;
  28. protected CellRendererPane rendererPane;
  29. // Listeners that are attached to the JTable
  30. protected MouseInputListener mouseInputListener;
  31. /**
  32. * This inner class is marked "public" due to a compiler bug.
  33. * This class should be treated as a "protected" inner class.
  34. * Instantiate it only within subclasses of BasicTableUI.
  35. */
  36. public class MouseInputHandler implements MouseInputListener {
  37. private boolean phantomMousePressed = false;
  38. private int lastEffectiveMouseX;
  39. public void mouseClicked(MouseEvent e) {}
  40. public void mousePressed(MouseEvent e) {
  41. if (phantomMousePressed) {
  42. // System.err.println("BasicTableHeaderUI recieved two consecutive mouse pressed events");
  43. // Catching this causes errors rather than fixing them.
  44. // return;
  45. }
  46. phantomMousePressed = true;
  47. header.setDraggedColumn(null);
  48. header.setResizingColumn(null);
  49. header.setDraggedDistance(0);
  50. Point p = e.getPoint();
  51. lastEffectiveMouseX = p.x;
  52. // First find which header cell was hit
  53. TableColumnModel columnModel = header.getColumnModel();
  54. int index = columnModel.getColumnIndexAtX(p.x);
  55. if (index != -1) {
  56. // The last 3 pixels + 3 pixels of next column are for resizing
  57. int resizeIndex = getResizingColumn(p);
  58. if (header.getResizingAllowed() && (resizeIndex != -1)) {
  59. TableColumn hitColumn = columnModel.getColumn(resizeIndex);
  60. header.setResizingColumn(hitColumn);
  61. }
  62. else if (header.getReorderingAllowed()) {
  63. TableColumn hitColumn = columnModel.getColumn(index);
  64. header.setDraggedColumn(hitColumn);
  65. }
  66. else { // Not allowed to reorder or resize.
  67. }
  68. }
  69. }
  70. public void mouseMoved(MouseEvent e) {
  71. if (getResizingColumn(e.getPoint()) != -1) {
  72. Cursor resizeCursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
  73. if (header.getCursor() != resizeCursor) {
  74. header.setCursor(resizeCursor);
  75. }
  76. }
  77. else {
  78. Cursor defaultCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
  79. if (header.getCursor() != defaultCursor) {
  80. header.setCursor(defaultCursor);
  81. }
  82. }
  83. }
  84. public void mouseDragged(MouseEvent e) {
  85. int mouseX = e.getX();
  86. int deltaX = mouseX - lastEffectiveMouseX;
  87. if (deltaX == 0) {
  88. return;
  89. }
  90. TableColumn resizingColumn = header.getResizingColumn();
  91. TableColumn draggedColumn = header.getDraggedColumn();
  92. if (resizingColumn != null) {
  93. int oldWidth = resizingColumn.getWidth();
  94. int newWidth = oldWidth + deltaX;
  95. resizingColumn.setWidth(newWidth);
  96. // PENDING(philip): Should't need to refer to the table here.
  97. int resizingColumnIndex = viewIndexForColumn(resizingColumn);
  98. header.getTable().sizeColumnsToFit(resizingColumnIndex);
  99. int acheivedDeltaX = resizingColumn.getWidth() - oldWidth;
  100. lastEffectiveMouseX = lastEffectiveMouseX + acheivedDeltaX;
  101. header.revalidate();
  102. header.repaint();
  103. if (header.getUpdateTableInRealTime()) {
  104. JTable table = header.getTable();
  105. table.revalidate();
  106. table.repaint();
  107. }
  108. }
  109. else if (draggedColumn != null) {
  110. move(e, deltaX);
  111. lastEffectiveMouseX = mouseX;
  112. }
  113. else {
  114. // Neither dragging nor resizing ...
  115. lastEffectiveMouseX = mouseX;
  116. }
  117. }
  118. public void mouseReleased(MouseEvent e) {
  119. phantomMousePressed = false;
  120. header.setResizingColumn(null);
  121. header.setDraggedColumn(null);
  122. header.setDraggedDistance(0);
  123. // Repaint to finish cleaning up
  124. header.repaint();
  125. JTable table = header.getTable();
  126. if (table != null)
  127. table.repaint();
  128. }
  129. public void mouseEntered(MouseEvent e) {}
  130. public void mouseExited(MouseEvent e) {}
  131. //
  132. // Protected & Private Methods
  133. //
  134. private int viewIndexForColumn(TableColumn aColumn) {
  135. TableColumnModel cm = header.getColumnModel();
  136. for (int column = 0; column < cm.getColumnCount(); column++) {
  137. if (cm.getColumn(column) == aColumn) {
  138. return column;
  139. }
  140. }
  141. return -1;
  142. }
  143. private void move(MouseEvent e, int delta) {
  144. TableColumnModel columnModel = header.getColumnModel();
  145. int lastColumn = columnModel.getColumnCount() - 1;
  146. TableColumn draggedColumn = header.getDraggedColumn();
  147. int draggedDistance = header.getDraggedDistance() + delta;
  148. int hitColumnIndex = viewIndexForColumn(draggedColumn);
  149. // Now check if we have moved enough to do a swap
  150. if ((draggedDistance < 0) && (hitColumnIndex != 0)) {
  151. // Moving left; check prevColumn
  152. int width = columnModel.getColumnMargin() +
  153. columnModel.getColumn(hitColumnIndex-1).getWidth();
  154. if (-draggedDistance > (width / 2)) {
  155. // Swap me
  156. columnModel.moveColumn(hitColumnIndex, hitColumnIndex-1);
  157. draggedDistance = width + draggedDistance;
  158. hitColumnIndex--;
  159. }
  160. }
  161. else if ((draggedDistance > 0) && (hitColumnIndex != lastColumn)) {
  162. // Moving right; check nextColumn
  163. int width = columnModel.getColumnMargin() +
  164. columnModel.getColumn(hitColumnIndex+1).getWidth();
  165. if (draggedDistance > (width / 2)) {
  166. // Swap me
  167. columnModel.moveColumn(hitColumnIndex, hitColumnIndex+1);
  168. draggedDistance = -(width - draggedDistance);
  169. hitColumnIndex++;
  170. }
  171. }
  172. // Redraw, compute how much we are moving and the total redraw rect
  173. Rectangle redrawRect = header.getHeaderRect(hitColumnIndex); // where I was
  174. redrawRect.x += header.getDraggedDistance();
  175. // draggedDistance += delta;
  176. Rectangle redrawRect2 = header.getHeaderRect(hitColumnIndex); // where I'm now
  177. redrawRect2.x += draggedDistance;
  178. redrawRect = redrawRect.union(redrawRect2); // Union the 2 rects
  179. header.repaint(redrawRect.x, 0, redrawRect.width, redrawRect.height);
  180. if (header.getUpdateTableInRealTime()) {
  181. JTable table = header.getTable();
  182. if (table != null)
  183. table.repaint(redrawRect.x, 0, redrawRect.width,
  184. (table.getRowHeight() +
  185. table.getIntercellSpacing().height)
  186. * table.getRowCount());
  187. }
  188. header.setDraggedColumn(columnModel.getColumn(hitColumnIndex));
  189. header.setDraggedDistance(draggedDistance);
  190. }
  191. private int getResizingColumn(Point p) {
  192. int column = 0;
  193. Rectangle resizeRect = new Rectangle(-3,0,6,header.getSize().height);
  194. int columnMargin = header.getColumnModel().getColumnMargin();
  195. Enumeration enumeration = header.getColumnModel().getColumns();
  196. while (enumeration.hasMoreElements()) {
  197. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  198. resizeRect.x += aColumn.getWidth() + columnMargin;
  199. if (resizeRect.x > p.x) {
  200. // Don't have to check the rest, we already gone past p
  201. break;
  202. }
  203. if (resizeRect.contains(p))
  204. return column;
  205. column++;
  206. }
  207. return -1;
  208. }
  209. }
  210. //
  211. // Factory methods for the Listeners
  212. //
  213. /**
  214. * Creates the mouse listener for the JTable.
  215. */
  216. protected MouseInputListener createMouseInputListener() {
  217. return new MouseInputHandler();
  218. }
  219. //
  220. // The installation/uninstall procedures and support
  221. //
  222. public static ComponentUI createUI(JComponent h) {
  223. return new BasicTableHeaderUI();
  224. }
  225. // Installation
  226. public void installUI(JComponent c) {
  227. header = (JTableHeader)c;
  228. rendererPane = new CellRendererPane();
  229. header.add(rendererPane);
  230. installDefaults();
  231. installListeners();
  232. installKeyboardActions();
  233. }
  234. /**
  235. * Initialize JTableHeader properties, e.g. font, foreground, and background.
  236. * The font, foreground, and background properties are only set if their
  237. * current value is either null or a UIResource, other properties are set
  238. * if the current value is null.
  239. *
  240. * @see #installUI
  241. */
  242. protected void installDefaults() {
  243. LookAndFeel.installColorsAndFont(header, "TableHeader.background",
  244. "TableHeader.foreground", "TableHeader.font");
  245. }
  246. /**
  247. * Attaches listeners to the JTableHeader.
  248. */
  249. protected void installListeners() {
  250. mouseInputListener = createMouseInputListener();
  251. header.addMouseListener(mouseInputListener);
  252. header.addMouseMotionListener(mouseInputListener);
  253. }
  254. /**
  255. * Register all keyboard actions on the JTableHeader.
  256. */
  257. protected void installKeyboardActions() { }
  258. // Uninstall methods
  259. public void uninstallUI(JComponent c) {
  260. uninstallDefaults();
  261. uninstallListeners();
  262. uninstallKeyboardActions();
  263. header.remove(rendererPane);
  264. rendererPane = null;
  265. header = null;
  266. }
  267. protected void uninstallDefaults() {}
  268. protected void uninstallListeners() {
  269. header.removeMouseListener(mouseInputListener);
  270. header.removeMouseMotionListener(mouseInputListener);
  271. mouseInputListener = null;
  272. }
  273. protected void uninstallKeyboardActions() {}
  274. //
  275. // Paint Methods and support
  276. //
  277. public void paint(Graphics g, JComponent c) {
  278. Rectangle clipBounds = g.getClipBounds();
  279. if (header.getColumnModel() == null)
  280. return;
  281. int column = 0;
  282. boolean drawn = false;
  283. int draggedColumnIndex = -1;
  284. Rectangle draggedCellRect = null;
  285. Dimension size = header.getSize();
  286. Rectangle cellRect = new Rectangle(0, 0, size.width, size.height);
  287. Enumeration enumeration = header.getColumnModel().getColumns();
  288. while (enumeration.hasMoreElements()) {
  289. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  290. int columnMargin = header.getColumnModel().getColumnMargin();
  291. cellRect.width = aColumn.getWidth() + columnMargin;
  292. // Note: The header cellRect includes columnMargin so the
  293. // drawing of header cells will not have any gaps.
  294. if (cellRect.intersects(clipBounds)) {
  295. drawn = true;
  296. if (aColumn != header.getDraggedColumn()) {
  297. paintCell(g, cellRect, column);
  298. }
  299. else {
  300. // Draw a gray well in place of the moving column
  301. g.setColor(header.getParent().getBackground());
  302. g.fillRect(cellRect.x, cellRect.y,
  303. cellRect.width, cellRect.height);
  304. draggedCellRect = new Rectangle(cellRect);
  305. draggedColumnIndex = column;
  306. }
  307. }
  308. else {
  309. if (drawn)
  310. // Don't need to iterate through the rest
  311. break;
  312. }
  313. cellRect.x += cellRect.width;
  314. column++;
  315. }
  316. // draw the dragged cell if we are dragging
  317. TableColumn draggedColumnObject = header.getDraggedColumn();
  318. if (draggedColumnObject != null && draggedCellRect != null) {
  319. draggedCellRect.x += header.getDraggedDistance();
  320. paintCell(g, draggedCellRect, draggedColumnIndex);
  321. }
  322. }
  323. private void paintCell(Graphics g, Rectangle cellRect, int columnIndex) {
  324. TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
  325. TableCellRenderer renderer = aColumn.getHeaderRenderer();
  326. Component component = renderer.getTableCellRendererComponent(
  327. header.getTable(), aColumn.getHeaderValue(),
  328. false, false, -1, columnIndex);
  329. rendererPane.add(component);
  330. rendererPane.paintComponent(g, component, header, cellRect.x, cellRect.y,
  331. cellRect.width, cellRect.height, true);
  332. }
  333. //
  334. // Size Methods
  335. //
  336. private int getHeaderHeight() {
  337. int height = 0;
  338. TableColumnModel columnModel = header.getColumnModel();
  339. for(int column = 0; column < columnModel.getColumnCount(); column++) {
  340. TableColumn aColumn = columnModel.getColumn(column);
  341. TableCellRenderer renderer = aColumn.getHeaderRenderer();
  342. Component comp = renderer.getTableCellRendererComponent(header.getTable(),
  343. aColumn.getHeaderValue(), false, false,
  344. -1, column);
  345. height = Math.max(height, comp.getPreferredSize().height);
  346. }
  347. return height;
  348. }
  349. private Dimension createHeaderSize(long width) {
  350. TableColumnModel columnModel = header.getColumnModel();
  351. // None of the callers include the intercell spacing, do it here.
  352. width += columnModel.getColumnMargin() * columnModel.getColumnCount();
  353. if (width > Integer.MAX_VALUE) {
  354. width = Integer.MAX_VALUE;
  355. }
  356. return new Dimension((int)width, getHeaderHeight());
  357. }
  358. /**
  359. * Return the minimum size of the header. The minimum width is the sum
  360. * of the minimum widths of each column (plus inter-cell spacing).
  361. */
  362. public Dimension getMinimumSize(JComponent c) {
  363. long width = 0;
  364. Enumeration enumeration = header.getColumnModel().getColumns();
  365. while (enumeration.hasMoreElements()) {
  366. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  367. width = width + aColumn.getMinWidth();
  368. }
  369. return createHeaderSize(width);
  370. }
  371. /**
  372. * Return the preferred size of the header. The preferred height is the
  373. * maximum of the preferred heights of all of the components provided
  374. * by the header renderers. The preferred width is the sum of the
  375. * preferred widths of each column (plus inter-cell spacing).
  376. */
  377. public Dimension getPreferredSize(JComponent c) {
  378. long width = 0;
  379. Enumeration enumeration = header.getColumnModel().getColumns();
  380. while (enumeration.hasMoreElements()) {
  381. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  382. width = width + aColumn.getPreferredWidth();
  383. }
  384. return createHeaderSize(width);
  385. }
  386. /**
  387. * Return the maximum size of the header. The maximum width is the sum
  388. * of the maximum widths of each column (plus inter-cell spacing).
  389. */
  390. public Dimension getMaximumSize(JComponent c) {
  391. long width = 0;
  392. Enumeration enumeration = header.getColumnModel().getColumns();
  393. while (enumeration.hasMoreElements()) {
  394. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  395. width = width + aColumn.getMaxWidth();
  396. }
  397. return createHeaderSize(width);
  398. }
  399. } // End of Class BasicTableHeaderUI