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