1. /*
  2. * @(#)SynthTableUI.java 1.17 04/07/23
  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.synth;
  8. import javax.swing.table.*;
  9. import javax.swing.*;
  10. import javax.swing.event.*;
  11. import java.util.Enumeration;
  12. import java.util.Hashtable;
  13. import java.util.TooManyListenersException;
  14. import java.awt.event.*;
  15. import java.awt.*;
  16. import java.awt.datatransfer.*;
  17. import java.awt.dnd.*;
  18. import java.text.*;
  19. import javax.swing.border.*;
  20. import javax.swing.plaf.*;
  21. import javax.swing.plaf.basic.*;
  22. import java.util.Date;
  23. import java.util.EventObject;
  24. import javax.swing.text.*;
  25. import java.beans.PropertyChangeEvent;
  26. import java.beans.PropertyChangeListener;
  27. import sun.swing.plaf.synth.SynthUI;
  28. /**
  29. * SynthTableUI implementation
  30. *
  31. * @version 1.17, 07/23/04
  32. * @author Philip Milne
  33. */
  34. class SynthTableUI extends BasicTableUI implements SynthUI,
  35. PropertyChangeListener {
  36. //
  37. // Instance Variables
  38. //
  39. private SynthStyle style;
  40. private boolean useTableColors;
  41. private boolean useUIBorder;
  42. // TableCellRenderer installed on the JTable at the time we're installed,
  43. // cached so that we can reinstall them at uninstallUI time.
  44. private TableCellRenderer dateRenderer;
  45. private TableCellRenderer numberRenderer;
  46. private TableCellRenderer doubleRender;
  47. private TableCellRenderer floatRenderer;
  48. private TableCellRenderer iconRenderer;
  49. private TableCellRenderer imageIconRenderer;
  50. private TableCellRenderer booleanRenderer;
  51. private TableCellRenderer objectRenderer;
  52. //
  53. // The installation/uninstall procedures and support
  54. //
  55. public static ComponentUI createUI(JComponent c) {
  56. return new SynthTableUI();
  57. }
  58. /**
  59. * Initialize JTable properties, e.g. font, foreground, and background.
  60. * The font, foreground, and background properties are only set if their
  61. * current value is either null or a UIResource, other properties are set
  62. * if the current value is null.
  63. *
  64. * @see #installUI
  65. */
  66. protected void installDefaults() {
  67. dateRenderer = installRendererIfPossible(Date.class, null);
  68. numberRenderer = installRendererIfPossible(Number.class, null);
  69. doubleRender = installRendererIfPossible(Double.class, null);
  70. floatRenderer = installRendererIfPossible(Float.class, null);
  71. iconRenderer = installRendererIfPossible(Icon.class, null);
  72. imageIconRenderer = installRendererIfPossible(ImageIcon.class, null);
  73. booleanRenderer = installRendererIfPossible(Boolean.class,
  74. new SynthBooleanTableCellRenderer());
  75. objectRenderer = installRendererIfPossible(Object.class,
  76. new SynthTableCellRenderer());
  77. updateStyle(table);
  78. }
  79. private TableCellRenderer installRendererIfPossible(Class objectClass,
  80. TableCellRenderer renderer) {
  81. TableCellRenderer currentRenderer = table.getDefaultRenderer(
  82. objectClass);
  83. if (currentRenderer instanceof UIResource) {
  84. table.setDefaultRenderer(objectClass, renderer);
  85. }
  86. return currentRenderer;
  87. }
  88. private void updateStyle(JTable c) {
  89. SynthContext context = getContext(c, ENABLED);
  90. SynthStyle oldStyle = style;
  91. style = SynthLookAndFeel.updateStyle(context, this);
  92. if (style != oldStyle) {
  93. context.setComponentState(ENABLED | SELECTED);
  94. Color sbg = table.getSelectionBackground();
  95. if (sbg == null || sbg instanceof UIResource) {
  96. table.setSelectionBackground(style.getColor(
  97. context, ColorType.TEXT_BACKGROUND));
  98. }
  99. Color sfg = table.getSelectionForeground();
  100. if (sfg == null || sfg instanceof UIResource) {
  101. table.setSelectionForeground(style.getColor(
  102. context, ColorType.TEXT_FOREGROUND));
  103. }
  104. context.setComponentState(ENABLED);
  105. Color gridColor = table.getGridColor();
  106. if (gridColor == null || gridColor instanceof UIResource) {
  107. gridColor = (Color)style.get(context, "Table.gridColor");
  108. if (gridColor == null) {
  109. gridColor = style.getColor(context, ColorType.FOREGROUND);
  110. }
  111. table.setGridColor(gridColor);
  112. }
  113. useTableColors = style.getBoolean(context,
  114. "Table.rendererUseTableColors", true);
  115. useUIBorder = style.getBoolean(context,
  116. "Table.rendererUseUIBorder", true);
  117. Object rowHeight = style.get(context, "Table.rowHeight");
  118. if (rowHeight != null) {
  119. LookAndFeel.installProperty(table, "rowHeight", rowHeight);
  120. }
  121. if (oldStyle != null) {
  122. uninstallKeyboardActions();
  123. installKeyboardActions();
  124. }
  125. }
  126. context.dispose();
  127. }
  128. /**
  129. * Attaches listeners to the JTable.
  130. */
  131. protected void installListeners() {
  132. super.installListeners();
  133. table.addPropertyChangeListener(this);
  134. }
  135. protected void uninstallDefaults() {
  136. table.setDefaultRenderer(Date.class, dateRenderer);
  137. table.setDefaultRenderer(Number.class, numberRenderer);
  138. table.setDefaultRenderer(Double.class, doubleRender);
  139. table.setDefaultRenderer(Float.class, floatRenderer);
  140. table.setDefaultRenderer(Icon.class, iconRenderer);
  141. table.setDefaultRenderer(ImageIcon.class, imageIconRenderer);
  142. table.setDefaultRenderer(Boolean.class, booleanRenderer);
  143. table.setDefaultRenderer(Object.class, objectRenderer);
  144. if (table.getTransferHandler() instanceof UIResource) {
  145. table.setTransferHandler(null);
  146. }
  147. SynthContext context = getContext(table, ENABLED);
  148. style.uninstallDefaults(context);
  149. context.dispose();
  150. style = null;
  151. }
  152. protected void uninstallListeners() {
  153. table.removePropertyChangeListener(this);
  154. super.uninstallListeners();
  155. }
  156. //
  157. // SynthUI
  158. //
  159. public SynthContext getContext(JComponent c) {
  160. return getContext(c, getComponentState(c));
  161. }
  162. private SynthContext getContext(JComponent c, int state) {
  163. return SynthContext.getContext(SynthContext.class, c,
  164. SynthLookAndFeel.getRegion(c), style, state);
  165. }
  166. private Region getRegion(JComponent c) {
  167. return SynthLookAndFeel.getRegion(c);
  168. }
  169. private int getComponentState(JComponent c) {
  170. return SynthLookAndFeel.getComponentState(c);
  171. }
  172. //
  173. // Paint methods and support
  174. //
  175. public void update(Graphics g, JComponent c) {
  176. SynthContext context = getContext(c);
  177. SynthLookAndFeel.update(context, g);
  178. context.getPainter().paintTableBackground(context,
  179. g, 0, 0, c.getWidth(), c.getHeight());
  180. paint(context, g);
  181. context.dispose();
  182. }
  183. public void paintBorder(SynthContext context, Graphics g, int x,
  184. int y, int w, int h) {
  185. context.getPainter().paintTableBorder(context, g, x, y, w, h);
  186. }
  187. public void paint(Graphics g, JComponent c) {
  188. SynthContext context = getContext(c);
  189. paint(context, g);
  190. context.dispose();
  191. }
  192. protected void paint(SynthContext context, Graphics g) {
  193. if (table.getRowCount() <= 0 || table.getColumnCount() <= 0) {
  194. return;
  195. }
  196. Rectangle clip = g.getClipBounds();
  197. Point upperLeft = clip.getLocation();
  198. Point lowerRight = new Point(clip.x + clip.width - 1, clip.y + clip.height - 1);
  199. int rMin = table.rowAtPoint(upperLeft);
  200. int rMax = table.rowAtPoint(lowerRight);
  201. // This should never happen.
  202. if (rMin == -1) {
  203. rMin = 0;
  204. }
  205. // If the table does not have enough rows to fill the view we'll get -1.
  206. // Replace this with the index of the last row.
  207. if (rMax == -1) {
  208. rMax = table.getRowCount()-1;
  209. }
  210. boolean ltr = table.getComponentOrientation().isLeftToRight();
  211. int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight);
  212. int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft);
  213. // This should never happen.
  214. if (cMin == -1) {
  215. cMin = 0;
  216. }
  217. // If the table does not have enough columns to fill the view we'll get -1.
  218. // Replace this with the index of the last column.
  219. if (cMax == -1) {
  220. cMax = table.getColumnCount()-1;
  221. }
  222. // Paint the grid.
  223. paintGrid(context, g, rMin, rMax, cMin, cMax);
  224. // Paint the cells.
  225. paintCells(context, g, rMin, rMax, cMin, cMax);
  226. }
  227. /*
  228. * Paints the grid lines within <I>aRect</I>, using the grid
  229. * color set with <I>setGridColor</I>. Paints vertical lines
  230. * if <code>getShowVerticalLines()</code> returns true and paints
  231. * horizontal lines if <code>getShowHorizontalLines()</code>
  232. * returns true.
  233. */
  234. private void paintGrid(SynthContext context, Graphics g, int rMin,
  235. int rMax, int cMin, int cMax) {
  236. g.setColor(table.getGridColor());
  237. Rectangle minCell = table.getCellRect(rMin, cMin, true);
  238. Rectangle maxCell = table.getCellRect(rMax, cMax, true);
  239. Rectangle damagedArea = minCell.union( maxCell );
  240. SynthGraphicsUtils synthG = context.getStyle().getGraphicsUtils(
  241. context);
  242. if (table.getShowHorizontalLines()) {
  243. int tableWidth = damagedArea.x + damagedArea.width;
  244. int y = damagedArea.y;
  245. for (int row = rMin; row <= rMax; row++) {
  246. y += table.getRowHeight(row);
  247. synthG.drawLine(context, "Table.grid",
  248. g, damagedArea.x, y - 1, tableWidth - 1,y - 1);
  249. }
  250. }
  251. if (table.getShowVerticalLines()) {
  252. TableColumnModel cm = table.getColumnModel();
  253. int tableHeight = damagedArea.y + damagedArea.height;
  254. int x;
  255. if (table.getComponentOrientation().isLeftToRight()) {
  256. x = damagedArea.x;
  257. for (int column = cMin; column <= cMax; column++) {
  258. int w = cm.getColumn(column).getWidth();
  259. x += w;
  260. synthG.drawLine(context, "Table.grid", g, x - 1, 0,
  261. x - 1, tableHeight - 1);
  262. }
  263. } else {
  264. x = damagedArea.x + damagedArea.width;
  265. for (int column = cMin; column < cMax; column++) {
  266. int w = cm.getColumn(column).getWidth();
  267. x -= w;
  268. synthG.drawLine(context, "Table.grid", g, x - 1, 0, x - 1,
  269. tableHeight - 1);
  270. }
  271. x -= cm.getColumn(cMax).getWidth();
  272. synthG.drawLine(context, "Table.grid", g, x, 0, x,
  273. tableHeight - 1);
  274. }
  275. }
  276. }
  277. private int viewIndexForColumn(TableColumn aColumn) {
  278. TableColumnModel cm = table.getColumnModel();
  279. for (int column = 0; column < cm.getColumnCount(); column++) {
  280. if (cm.getColumn(column) == aColumn) {
  281. return column;
  282. }
  283. }
  284. return -1;
  285. }
  286. private void paintCells(SynthContext context, Graphics g, int rMin,
  287. int rMax, int cMin, int cMax) {
  288. JTableHeader header = table.getTableHeader();
  289. TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
  290. TableColumnModel cm = table.getColumnModel();
  291. int columnMargin = cm.getColumnMargin();
  292. Rectangle cellRect;
  293. TableColumn aColumn;
  294. int columnWidth;
  295. if (table.getComponentOrientation().isLeftToRight()) {
  296. for(int row = rMin; row <= rMax; row++) {
  297. cellRect = table.getCellRect(row, cMin, false);
  298. for(int column = cMin; column <= cMax; column++) {
  299. aColumn = cm.getColumn(column);
  300. columnWidth = aColumn.getWidth();
  301. cellRect.width = columnWidth - columnMargin;
  302. if (aColumn != draggedColumn) {
  303. paintCell(context, g, cellRect, row, column);
  304. }
  305. cellRect.x += columnWidth;
  306. }
  307. }
  308. } else {
  309. for(int row = rMin; row <= rMax; row++) {
  310. cellRect = table.getCellRect(row, cMin, false);
  311. aColumn = cm.getColumn(cMin);
  312. if (aColumn != draggedColumn) {
  313. columnWidth = aColumn.getWidth();
  314. cellRect.width = columnWidth - columnMargin;
  315. paintCell(context, g, cellRect, row, cMin);
  316. }
  317. for(int column = cMin+1; column <= cMax; column++) {
  318. aColumn = cm.getColumn(column);
  319. columnWidth = aColumn.getWidth();
  320. cellRect.width = columnWidth - columnMargin;
  321. cellRect.x -= columnWidth;
  322. if (aColumn != draggedColumn) {
  323. paintCell(context, g, cellRect, row, column);
  324. }
  325. }
  326. }
  327. }
  328. // Paint the dragged column if we are dragging.
  329. if (draggedColumn != null) {
  330. paintDraggedArea(context, g, rMin, rMax, draggedColumn, header.getDraggedDistance());
  331. }
  332. // Remove any renderers that may be left in the rendererPane.
  333. rendererPane.removeAll();
  334. }
  335. private void paintDraggedArea(SynthContext context, Graphics g, int rMin, int rMax, TableColumn draggedColumn, int distance) {
  336. int draggedColumnIndex = viewIndexForColumn(draggedColumn);
  337. Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
  338. Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);
  339. Rectangle vacatedColumnRect = minCell.union(maxCell);
  340. // Paint a gray well in place of the moving column.
  341. g.setColor(table.getParent().getBackground());
  342. g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
  343. vacatedColumnRect.width, vacatedColumnRect.height);
  344. // Move to the where the cell has been dragged.
  345. vacatedColumnRect.x += distance;
  346. // Fill the background.
  347. g.setColor(context.getStyle().getColor(context, ColorType.BACKGROUND));
  348. g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
  349. vacatedColumnRect.width, vacatedColumnRect.height);
  350. SynthGraphicsUtils synthG = context.getStyle().getGraphicsUtils(
  351. context);
  352. // Paint the vertical grid lines if necessary.
  353. if (table.getShowVerticalLines()) {
  354. g.setColor(table.getGridColor());
  355. int x1 = vacatedColumnRect.x;
  356. int y1 = vacatedColumnRect.y;
  357. int x2 = x1 + vacatedColumnRect.width - 1;
  358. int y2 = y1 + vacatedColumnRect.height - 1;
  359. // Left
  360. synthG.drawLine(context, "Table.grid", g, x1-1, y1, x1-1, y2);
  361. // Right
  362. synthG.drawLine(context, "Table.grid", g, x2, y1, x2, y2);
  363. }
  364. for(int row = rMin; row <= rMax; row++) {
  365. // Render the cell value
  366. Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
  367. r.x += distance;
  368. paintCell(context, g, r, row, draggedColumnIndex);
  369. // Paint the (lower) horizontal grid line if necessary.
  370. if (table.getShowHorizontalLines()) {
  371. g.setColor(table.getGridColor());
  372. Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
  373. rcr.x += distance;
  374. int x1 = rcr.x;
  375. int y1 = rcr.y;
  376. int x2 = x1 + rcr.width - 1;
  377. int y2 = y1 + rcr.height - 1;
  378. synthG.drawLine(context, "Table.grid", g, x1, y2, x2, y2);
  379. }
  380. }
  381. }
  382. private void paintCell(SynthContext context, Graphics g,
  383. Rectangle cellRect, int row, int column) {
  384. if (table.isEditing() && table.getEditingRow()==row &&
  385. table.getEditingColumn()==column) {
  386. Component component = table.getEditorComponent();
  387. component.setBounds(cellRect);
  388. component.validate();
  389. }
  390. else {
  391. TableCellRenderer renderer = table.getCellRenderer(row, column);
  392. Component component = table.prepareRenderer(renderer, row, column);
  393. rendererPane.paintComponent(g, component, table, cellRect.x,
  394. cellRect.y, cellRect.width, cellRect.height, true);
  395. }
  396. }
  397. public void propertyChange(PropertyChangeEvent event) {
  398. if (SynthLookAndFeel.shouldUpdateStyle(event)) {
  399. updateStyle((JTable)event.getSource());
  400. }
  401. }
  402. private class SynthBooleanTableCellRenderer extends JCheckBox implements
  403. TableCellRenderer {
  404. public SynthBooleanTableCellRenderer() {
  405. super();
  406. setHorizontalAlignment(JLabel.CENTER);
  407. }
  408. public String getName() {
  409. String name = super.getName();
  410. if (name == null) {
  411. return "Table.cellRenderer";
  412. }
  413. return name;
  414. }
  415. public Component getTableCellRendererComponent(
  416. JTable table, Object value, boolean isSelected,
  417. boolean hasFocus, int row, int column) {
  418. if (isSelected) {
  419. setForeground(table.getSelectionForeground());
  420. setBackground(table.getSelectionBackground());
  421. }
  422. else {
  423. setForeground(table.getForeground());
  424. setBackground(table.getBackground());
  425. }
  426. setSelected((value != null && ((Boolean)value).booleanValue()));
  427. // NOTE: We don't do this as otherwise the the JCheckBox will
  428. // think it is selected when it may not be. This means JCheckBox
  429. // renderers don't render the selection correctly.
  430. /*
  431. if (!useTableColors && (isSelected || hasFocus)) {
  432. SynthLookAndFeel.setSelectedUI((SynthButtonUI)SynthLookAndFeel.
  433. getUIOfType(getUI(), SynthButtonUI.class),
  434. isSelected, hasFocus, table.isEnabled());
  435. }
  436. else {
  437. SynthLookAndFeel.resetSelectedUI();
  438. }
  439. */
  440. return this;
  441. }
  442. public void paint(Graphics g) {
  443. super.paint(g);
  444. // Refer to comment above for why this is commented out.
  445. // SynthLookAndFeel.resetSelectedUI();
  446. }
  447. }
  448. private class SynthTableCellRenderer extends DefaultTableCellRenderer {
  449. private Object numberFormat;
  450. private Object dateFormat;
  451. private boolean opaque;
  452. public void setOpaque(boolean isOpaque) {
  453. opaque = isOpaque;
  454. }
  455. public boolean isOpaque() {
  456. return opaque;
  457. }
  458. public String getName() {
  459. String name = super.getName();
  460. if (name == null) {
  461. return "Table.cellRenderer";
  462. }
  463. return name;
  464. }
  465. public void setBorder(Border b) {
  466. if (useUIBorder || b instanceof SynthBorder) {
  467. super.setBorder(b);
  468. }
  469. }
  470. public Component getTableCellRendererComponent(
  471. JTable table, Object value, boolean isSelected,
  472. boolean hasFocus, int row, int column) {
  473. if (!useTableColors && (isSelected || hasFocus)) {
  474. SynthLookAndFeel.setSelectedUI((SynthLabelUI)SynthLookAndFeel.
  475. getUIOfType(getUI(), SynthLabelUI.class),
  476. isSelected, hasFocus, table.isEnabled());
  477. }
  478. else {
  479. SynthLookAndFeel.resetSelectedUI();
  480. }
  481. super.getTableCellRendererComponent(table, value, isSelected,
  482. hasFocus, row, column);
  483. setIcon(null);
  484. Class columnClass = table.getColumnClass(column);
  485. configureValue(value, columnClass);
  486. return this;
  487. }
  488. private void configureValue(Object value, Class columnClass) {
  489. if (columnClass == Object.class || columnClass == null) {
  490. setHorizontalAlignment(JLabel.LEADING);
  491. } else if (columnClass == Float.class || columnClass == Double.class) {
  492. if (numberFormat == null) {
  493. numberFormat = NumberFormat.getInstance();
  494. }
  495. setHorizontalAlignment(JLabel.TRAILING);
  496. setText((value == null) ? "" : ((NumberFormat)numberFormat).format(value));
  497. }
  498. else if (columnClass == Number.class) {
  499. setHorizontalAlignment(JLabel.TRAILING);
  500. // Super will have set value.
  501. }
  502. else if (columnClass == Icon.class || columnClass == ImageIcon.class) {
  503. setHorizontalAlignment(JLabel.CENTER);
  504. setIcon((Icon)value);
  505. setText("");
  506. }
  507. else if (columnClass == Date.class) {
  508. if (dateFormat == null) {
  509. dateFormat = DateFormat.getDateInstance();
  510. }
  511. setHorizontalAlignment(JLabel.LEADING);
  512. setText((value == null) ? "" : ((Format)dateFormat).format(value));
  513. }
  514. else {
  515. configureValue(value, columnClass.getSuperclass());
  516. }
  517. }
  518. public void paint(Graphics g) {
  519. super.paint(g);
  520. SynthLookAndFeel.resetSelectedUI();
  521. }
  522. }
  523. }