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