1. /*
  2. * @(#)BasicTableHeaderUI.java 1.63 03/12/19
  3. *
  4. * Copyright 2004 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.63 12/19/03
  19. * @author Alan Chung
  20. * @author Philip Milne
  21. */
  22. public class BasicTableHeaderUI extends TableHeaderUI {
  23. private static Cursor resizeCursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
  24. //
  25. // Instance Variables
  26. //
  27. /** The JTableHeader that is delegating the painting to this UI. */
  28. protected JTableHeader header;
  29. protected CellRendererPane rendererPane;
  30. // Listeners that are attached to the JTable
  31. protected MouseInputListener mouseInputListener;
  32. /**
  33. * This inner class is marked "public" due to a compiler bug.
  34. * This class should be treated as a "protected" inner class.
  35. * Instantiate it only within subclasses of BasicTableUI.
  36. */
  37. public class MouseInputHandler implements MouseInputListener {
  38. private int mouseXOffset;
  39. private Cursor otherCursor = resizeCursor;
  40. public void mouseClicked(MouseEvent e) {}
  41. private boolean canResize(TableColumn column) {
  42. return (column != null) && header.getResizingAllowed() && column.getResizable();
  43. }
  44. private TableColumn getResizingColumn(Point p) {
  45. return getResizingColumn(p, header.columnAtPoint(p));
  46. }
  47. private TableColumn getResizingColumn(Point p, int column) {
  48. if (column == -1) {
  49. return null;
  50. }
  51. Rectangle r = header.getHeaderRect(column);
  52. r.grow(-3, 0);
  53. if (r.contains(p)) {
  54. return null;
  55. }
  56. int midPoint = r.x + r.width2;
  57. int columnIndex;
  58. if( header.getComponentOrientation().isLeftToRight() ) {
  59. columnIndex = (p.x < midPoint) ? column - 1 : column;
  60. } else {
  61. columnIndex = (p.x < midPoint) ? column : column - 1;
  62. }
  63. if (columnIndex == -1) {
  64. return null;
  65. }
  66. return header.getColumnModel().getColumn(columnIndex);
  67. }
  68. public void mousePressed(MouseEvent e) {
  69. header.setDraggedColumn(null);
  70. header.setResizingColumn(null);
  71. header.setDraggedDistance(0);
  72. Point p = e.getPoint();
  73. // First find which header cell was hit
  74. TableColumnModel columnModel = header.getColumnModel();
  75. int index = header.columnAtPoint(p);
  76. if (index != -1) {
  77. // The last 3 pixels + 3 pixels of next column are for resizing
  78. TableColumn resizingColumn = getResizingColumn(p, index);
  79. if (canResize(resizingColumn)) {
  80. header.setResizingColumn(resizingColumn);
  81. if( header.getComponentOrientation().isLeftToRight() ) {
  82. mouseXOffset = p.x - resizingColumn.getWidth();
  83. } else {
  84. mouseXOffset = p.x + resizingColumn.getWidth();
  85. }
  86. }
  87. else if (header.getReorderingAllowed()) {
  88. TableColumn hitColumn = columnModel.getColumn(index);
  89. header.setDraggedColumn(hitColumn);
  90. mouseXOffset = p.x;
  91. }
  92. }
  93. }
  94. private void swapCursor() {
  95. Cursor tmp = header.getCursor();
  96. header.setCursor(otherCursor);
  97. otherCursor = tmp;
  98. }
  99. public void mouseMoved(MouseEvent e) {
  100. if (canResize(getResizingColumn(e.getPoint())) !=
  101. (header.getCursor() == resizeCursor)) {
  102. swapCursor();
  103. }
  104. }
  105. public void mouseDragged(MouseEvent e) {
  106. int mouseX = e.getX();
  107. TableColumn resizingColumn = header.getResizingColumn();
  108. TableColumn draggedColumn = header.getDraggedColumn();
  109. boolean headerLeftToRight = header.getComponentOrientation().isLeftToRight();
  110. if (resizingColumn != null) {
  111. int oldWidth = resizingColumn.getWidth();
  112. int newWidth;
  113. if (headerLeftToRight) {
  114. newWidth = mouseX - mouseXOffset;
  115. } else {
  116. newWidth = mouseXOffset - mouseX;
  117. }
  118. resizingColumn.setWidth(newWidth);
  119. Container container;
  120. if ((header.getParent() == null) ||
  121. ((container = header.getParent().getParent()) == null) ||
  122. !(container instanceof JScrollPane)) {
  123. return;
  124. }
  125. if (!container.getComponentOrientation().isLeftToRight() &&
  126. !headerLeftToRight) {
  127. JTable table = header.getTable();
  128. if (table != null) {
  129. JViewport viewport = ((JScrollPane)container).getViewport();
  130. int viewportWidth = viewport.getWidth();
  131. int diff = newWidth - oldWidth;
  132. int newHeaderWidth = table.getWidth() + diff;
  133. /* Resize a table */
  134. Dimension tableSize = table.getSize();
  135. tableSize.width += diff;
  136. table.setSize(tableSize);
  137. /* If this table is in AUTO_RESIZE_OFF mode and
  138. * has a horizontal scrollbar, we need to update
  139. * a view's position.
  140. */
  141. if ((newHeaderWidth >= viewportWidth) &&
  142. (table.getAutoResizeMode() == JTable.AUTO_RESIZE_OFF)) {
  143. Point p = viewport.getViewPosition();
  144. p.x = Math.max(0, Math.min(newHeaderWidth - viewportWidth, p.x + diff));
  145. viewport.setViewPosition(p);
  146. /* Update the original X offset value. */
  147. mouseXOffset += diff;
  148. }
  149. }
  150. }
  151. }
  152. else if (draggedColumn != null) {
  153. TableColumnModel cm = header.getColumnModel();
  154. int draggedDistance = mouseX - mouseXOffset;
  155. int direction = (draggedDistance < 0) ? -1 : 1;
  156. int columnIndex = viewIndexForColumn(draggedColumn);
  157. int newColumnIndex = columnIndex + (headerLeftToRight ? direction : -direction);
  158. if (0 <= newColumnIndex && newColumnIndex < cm.getColumnCount()) {
  159. int width = cm.getColumn(newColumnIndex).getWidth();
  160. if (Math.abs(draggedDistance) > (width / 2)) {
  161. mouseXOffset = mouseXOffset + direction * width;
  162. header.setDraggedDistance(draggedDistance - direction * width);
  163. cm.moveColumn(columnIndex, newColumnIndex);
  164. return;
  165. }
  166. }
  167. setDraggedDistance(draggedDistance, columnIndex);
  168. }
  169. }
  170. public void mouseReleased(MouseEvent e) {
  171. setDraggedDistance(0, viewIndexForColumn(header.getDraggedColumn()));
  172. header.setResizingColumn(null);
  173. header.setDraggedColumn(null);
  174. }
  175. public void mouseEntered(MouseEvent e) {}
  176. public void mouseExited(MouseEvent e) {}
  177. //
  178. // Protected & Private Methods
  179. //
  180. private void setDraggedDistance(int draggedDistance, int column) {
  181. header.setDraggedDistance(draggedDistance);
  182. if (column != -1) {
  183. header.getColumnModel().moveColumn(column, column);
  184. }
  185. }
  186. }
  187. //
  188. // Factory methods for the Listeners
  189. //
  190. /**
  191. * Creates the mouse listener for the JTable.
  192. */
  193. protected MouseInputListener createMouseInputListener() {
  194. return new MouseInputHandler();
  195. }
  196. //
  197. // The installation/uninstall procedures and support
  198. //
  199. public static ComponentUI createUI(JComponent h) {
  200. return new BasicTableHeaderUI();
  201. }
  202. // Installation
  203. public void installUI(JComponent c) {
  204. header = (JTableHeader)c;
  205. rendererPane = new CellRendererPane();
  206. header.add(rendererPane);
  207. installDefaults();
  208. installListeners();
  209. installKeyboardActions();
  210. }
  211. /**
  212. * Initialize JTableHeader properties, e.g. font, foreground, and background.
  213. * The font, foreground, and background properties are only set if their
  214. * current value is either null or a UIResource, other properties are set
  215. * if the current value is null.
  216. *
  217. * @see #installUI
  218. */
  219. protected void installDefaults() {
  220. LookAndFeel.installColorsAndFont(header, "TableHeader.background",
  221. "TableHeader.foreground", "TableHeader.font");
  222. LookAndFeel.installProperty(header, "opaque", Boolean.TRUE);
  223. }
  224. /**
  225. * Attaches listeners to the JTableHeader.
  226. */
  227. protected void installListeners() {
  228. mouseInputListener = createMouseInputListener();
  229. header.addMouseListener(mouseInputListener);
  230. header.addMouseMotionListener(mouseInputListener);
  231. }
  232. /**
  233. * Register all keyboard actions on the JTableHeader.
  234. */
  235. protected void installKeyboardActions() { }
  236. // Uninstall methods
  237. public void uninstallUI(JComponent c) {
  238. uninstallDefaults();
  239. uninstallListeners();
  240. uninstallKeyboardActions();
  241. header.remove(rendererPane);
  242. rendererPane = null;
  243. header = null;
  244. }
  245. protected void uninstallDefaults() {}
  246. protected void uninstallListeners() {
  247. header.removeMouseListener(mouseInputListener);
  248. header.removeMouseMotionListener(mouseInputListener);
  249. mouseInputListener = null;
  250. }
  251. protected void uninstallKeyboardActions() {}
  252. //
  253. // Paint Methods and support
  254. //
  255. public void paint(Graphics g, JComponent c) {
  256. if (header.getColumnModel().getColumnCount() <= 0) {
  257. return;
  258. }
  259. boolean ltr = header.getComponentOrientation().isLeftToRight();
  260. Rectangle clip = g.getClipBounds();
  261. Point left = clip.getLocation();
  262. Point right = new Point( clip.x + clip.width - 1, clip.y );
  263. TableColumnModel cm = header.getColumnModel();
  264. int cMin = header.columnAtPoint( ltr ? left : right );
  265. int cMax = header.columnAtPoint( ltr ? right : left );
  266. // This should never happen.
  267. if (cMin == -1) {
  268. cMin = 0;
  269. }
  270. // If the table does not have enough columns to fill the view we'll get -1.
  271. // Replace this with the index of the last column.
  272. if (cMax == -1) {
  273. cMax = cm.getColumnCount()-1;
  274. }
  275. TableColumn draggedColumn = header.getDraggedColumn();
  276. int columnWidth;
  277. Rectangle cellRect = header.getHeaderRect(ltr ? cMin : cMax);
  278. TableColumn aColumn;
  279. if (ltr) {
  280. for(int column = cMin; column <= cMax ; column++) {
  281. aColumn = cm.getColumn(column);
  282. columnWidth = aColumn.getWidth();
  283. cellRect.width = columnWidth;
  284. if (aColumn != draggedColumn) {
  285. paintCell(g, cellRect, column);
  286. }
  287. cellRect.x += columnWidth;
  288. }
  289. } else {
  290. for(int column = cMax; column >= cMin; column--) {
  291. aColumn = cm.getColumn(column);
  292. columnWidth = aColumn.getWidth();
  293. cellRect.width = columnWidth;
  294. if (aColumn != draggedColumn) {
  295. paintCell(g, cellRect, column);
  296. }
  297. cellRect.x += columnWidth;
  298. }
  299. }
  300. // Paint the dragged column if we are dragging.
  301. if (draggedColumn != null) {
  302. int draggedColumnIndex = viewIndexForColumn(draggedColumn);
  303. Rectangle draggedCellRect = header.getHeaderRect(draggedColumnIndex);
  304. // Draw a gray well in place of the moving column.
  305. g.setColor(header.getParent().getBackground());
  306. g.fillRect(draggedCellRect.x, draggedCellRect.y,
  307. draggedCellRect.width, draggedCellRect.height);
  308. draggedCellRect.x += header.getDraggedDistance();
  309. // Fill the background.
  310. g.setColor(header.getBackground());
  311. g.fillRect(draggedCellRect.x, draggedCellRect.y,
  312. draggedCellRect.width, draggedCellRect.height);
  313. paintCell(g, draggedCellRect, draggedColumnIndex);
  314. }
  315. // Remove all components in the rendererPane.
  316. rendererPane.removeAll();
  317. }
  318. private Component getHeaderRenderer(int columnIndex) {
  319. TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
  320. TableCellRenderer renderer = aColumn.getHeaderRenderer();
  321. if (renderer == null) {
  322. renderer = header.getDefaultRenderer();
  323. }
  324. return renderer.getTableCellRendererComponent(header.getTable(),
  325. aColumn.getHeaderValue(), false, false,
  326. -1, columnIndex);
  327. }
  328. private void paintCell(Graphics g, Rectangle cellRect, int columnIndex) {
  329. Component component = getHeaderRenderer(columnIndex);
  330. rendererPane.paintComponent(g, component, header, cellRect.x, cellRect.y,
  331. cellRect.width, cellRect.height, true);
  332. }
  333. private int viewIndexForColumn(TableColumn aColumn) {
  334. TableColumnModel cm = header.getColumnModel();
  335. for (int column = 0; column < cm.getColumnCount(); column++) {
  336. if (cm.getColumn(column) == aColumn) {
  337. return column;
  338. }
  339. }
  340. return -1;
  341. }
  342. //
  343. // Size Methods
  344. //
  345. private int getHeaderHeight() {
  346. int height = 0;
  347. boolean accomodatedDefault = false;
  348. TableColumnModel columnModel = header.getColumnModel();
  349. for(int column = 0; column < columnModel.getColumnCount(); column++) {
  350. TableColumn aColumn = columnModel.getColumn(column);
  351. // Configuring the header renderer to calculate its preferred size is expensive.
  352. // Optimise this by assuming the default renderer always has the same height.
  353. if (aColumn.getHeaderRenderer() != null || !accomodatedDefault) {
  354. Component comp = getHeaderRenderer(column);
  355. int rendererHeight = comp.getPreferredSize().height;
  356. height = Math.max(height, rendererHeight);
  357. // If the header value is empty (== "") in the
  358. // first column (and this column is set up
  359. // to use the default renderer) we will
  360. // return zero from this routine and the header
  361. // will disappear altogether. Avoiding the calculation
  362. // of the preferred size is such a performance win for
  363. // most applications that we will continue to
  364. // use this cheaper calculation, handling these
  365. // issues as `edge cases'.
  366. if (rendererHeight > 0) {
  367. accomodatedDefault = true;
  368. }
  369. }
  370. }
  371. return height;
  372. }
  373. private Dimension createHeaderSize(long width) {
  374. TableColumnModel columnModel = header.getColumnModel();
  375. // None of the callers include the intercell spacing, do it here.
  376. if (width > Integer.MAX_VALUE) {
  377. width = Integer.MAX_VALUE;
  378. }
  379. return new Dimension((int)width, getHeaderHeight());
  380. }
  381. /**
  382. * Return the minimum size of the header. The minimum width is the sum
  383. * of the minimum widths of each column (plus inter-cell spacing).
  384. */
  385. public Dimension getMinimumSize(JComponent c) {
  386. long width = 0;
  387. Enumeration enumeration = header.getColumnModel().getColumns();
  388. while (enumeration.hasMoreElements()) {
  389. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  390. width = width + aColumn.getMinWidth();
  391. }
  392. return createHeaderSize(width);
  393. }
  394. /**
  395. * Return the preferred size of the header. The preferred height is the
  396. * maximum of the preferred heights of all of the components provided
  397. * by the header renderers. The preferred width is the sum of the
  398. * preferred widths of each column (plus inter-cell spacing).
  399. */
  400. public Dimension getPreferredSize(JComponent c) {
  401. long width = 0;
  402. Enumeration enumeration = header.getColumnModel().getColumns();
  403. while (enumeration.hasMoreElements()) {
  404. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  405. width = width + aColumn.getPreferredWidth();
  406. }
  407. return createHeaderSize(width);
  408. }
  409. /**
  410. * Return the maximum size of the header. The maximum width is the sum
  411. * of the maximum widths of each column (plus inter-cell spacing).
  412. */
  413. public Dimension getMaximumSize(JComponent c) {
  414. long width = 0;
  415. Enumeration enumeration = header.getColumnModel().getColumns();
  416. while (enumeration.hasMoreElements()) {
  417. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  418. width = width + aColumn.getMaxWidth();
  419. }
  420. return createHeaderSize(width);
  421. }
  422. } // End of Class BasicTableHeaderUI