1. /*
  2. * @(#)SynthTreeUI.java 1.23 04/05/27
  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.*;
  9. import javax.swing.event.*;
  10. import java.awt.*;
  11. import java.awt.event.*;
  12. import java.awt.datatransfer.*;
  13. import java.awt.dnd.*;
  14. import java.beans.*;
  15. import java.io.*;
  16. import java.util.*;
  17. import javax.swing.plaf.*;
  18. import javax.swing.plaf.basic.*;
  19. import javax.swing.tree.*;
  20. import javax.swing.text.Position;
  21. import sun.swing.plaf.synth.*;
  22. /**
  23. * Skinnable TreeUI.
  24. *
  25. * @version 1.23, 05/27/04
  26. * @author Scott Violet
  27. */
  28. class SynthTreeUI extends BasicTreeUI implements PropertyChangeListener,
  29. SynthUI {
  30. private SynthStyle style;
  31. private SynthStyle cellStyle;
  32. private SynthContext paintContext;
  33. private boolean drawHorizontalLines;
  34. private boolean drawVerticalLines;
  35. private int leadRow;
  36. private int padding;
  37. private boolean useTreeColors;
  38. private Icon expandedIconWrapper;
  39. public static ComponentUI createUI(JComponent x) {
  40. return new SynthTreeUI();
  41. }
  42. SynthTreeUI() {
  43. expandedIconWrapper = new ExpandedIconWrapper();
  44. }
  45. public Icon getExpandedIcon() {
  46. return expandedIconWrapper;
  47. }
  48. protected void installDefaults() {
  49. updateStyle(tree);
  50. }
  51. private void updateStyle(JTree tree) {
  52. SynthContext context = getContext(tree, ENABLED);
  53. SynthStyle oldStyle = style;
  54. style = SynthLookAndFeel.updateStyle(context, this);
  55. if (style != oldStyle) {
  56. Object value;
  57. setExpandedIcon(style.getIcon(context, "Tree.expandedIcon"));
  58. setCollapsedIcon(style.getIcon(context, "Tree.collapsedIcon"));
  59. setLeftChildIndent(style.getInt(context, "Tree.leftChildIndent",
  60. 0));
  61. setRightChildIndent(style.getInt(context, "Tree.rightChildIndent",
  62. 0));
  63. drawHorizontalLines = style.getBoolean(
  64. context, "Tree.drawHorizontalLines",true);
  65. drawVerticalLines = style.getBoolean(
  66. context, "Tree.drawVerticalLines", true);
  67. value = style.get(context, "Tree.rowHeight");
  68. if (value != null) {
  69. LookAndFeel.installProperty(tree, "rowHeight", value);
  70. }
  71. value = style.get(context, "Tree.scrollsOnExpand");
  72. LookAndFeel.installProperty(tree, "scrollsOnExpand",
  73. value != null? value : Boolean.TRUE);
  74. padding = style.getInt(context, "Tree.padding", 0);
  75. largeModel = (tree.isLargeModel() && tree.getRowHeight() > 0);
  76. useTreeColors = style.getBoolean(context,
  77. "Tree.rendererUseTreeColors", true);
  78. Boolean showsRootHandles = style.getBoolean(
  79. context, "Tree.showsRootHandles", Boolean.TRUE);
  80. LookAndFeel.installProperty(
  81. tree, JTree.SHOWS_ROOT_HANDLES_PROPERTY, showsRootHandles);
  82. if (oldStyle != null) {
  83. uninstallKeyboardActions();
  84. installKeyboardActions();
  85. }
  86. }
  87. context.dispose();
  88. context = getContext(tree, Region.TREE_CELL, ENABLED);
  89. cellStyle = SynthLookAndFeel.updateStyle(context, this);
  90. context.dispose();
  91. }
  92. protected void installListeners() {
  93. super.installListeners();
  94. tree.addPropertyChangeListener(this);
  95. }
  96. public SynthContext getContext(JComponent c) {
  97. return getContext(c, getComponentState(c));
  98. }
  99. private SynthContext getContext(JComponent c, int state) {
  100. return SynthContext.getContext(SynthContext.class, c,
  101. SynthLookAndFeel.getRegion(c), style, state);
  102. }
  103. private Region getRegion(JTree c) {
  104. return SynthLookAndFeel.getRegion(c);
  105. }
  106. private int getComponentState(JComponent c) {
  107. return SynthLookAndFeel.getComponentState(c);
  108. }
  109. private SynthContext getContext(JComponent c, Region region) {
  110. return getContext(c, region, getComponentState(c, region));
  111. }
  112. private SynthContext getContext(JComponent c, Region region, int state) {
  113. return SynthContext.getContext(SynthContext.class, c,
  114. region, cellStyle, state);
  115. }
  116. private int getComponentState(JComponent c, Region region) {
  117. // Always treat the cell as selected, will be adjusted appropriately
  118. // when painted.
  119. return ENABLED | SELECTED;
  120. }
  121. protected TreeCellEditor createDefaultCellEditor() {
  122. TreeCellRenderer renderer = tree.getCellRenderer();
  123. DefaultTreeCellEditor editor;
  124. if(renderer != null && (renderer instanceof DefaultTreeCellRenderer)) {
  125. editor = new SynthTreeCellEditor(tree, (DefaultTreeCellRenderer)
  126. renderer);
  127. }
  128. else {
  129. editor = new SynthTreeCellEditor(tree, null);
  130. }
  131. return editor;
  132. }
  133. protected TreeCellRenderer createDefaultCellRenderer() {
  134. return new SynthTreeCellRenderer();
  135. }
  136. protected void uninstallDefaults() {
  137. SynthContext context = getContext(tree, ENABLED);
  138. style.uninstallDefaults(context);
  139. context.dispose();
  140. style = null;
  141. context = getContext(tree, Region.TREE_CELL, ENABLED);
  142. cellStyle.uninstallDefaults(context);
  143. context.dispose();
  144. cellStyle = null;
  145. if (tree.getTransferHandler() instanceof UIResource) {
  146. tree.setTransferHandler(null);
  147. }
  148. }
  149. protected void uninstallListeners() {
  150. super.uninstallListeners();
  151. tree.removePropertyChangeListener(this);
  152. }
  153. public void update(Graphics g, JComponent c) {
  154. SynthContext context = getContext(c);
  155. SynthLookAndFeel.update(context, g);
  156. context.getPainter().paintTreeBackground(context,
  157. g, 0, 0, c.getWidth(), c.getHeight());
  158. paint(context, g);
  159. context.dispose();
  160. }
  161. public void paintBorder(SynthContext context, Graphics g, int x,
  162. int y, int w, int h) {
  163. context.getPainter().paintTreeBorder(context, g, x, y, w, h);
  164. }
  165. public void paint(Graphics g, JComponent c) {
  166. SynthContext context = getContext(c);
  167. paint(context, g);
  168. context.dispose();
  169. }
  170. private void adjustCellBounds(JTree tree, Rectangle bounds, Insets i){
  171. if (bounds != null) {
  172. if (i == null) {
  173. i = SynthLookAndFeel.EMPTY_UIRESOURCE_INSETS;
  174. }
  175. bounds.x += i.left;
  176. bounds.y += i.top;
  177. }
  178. }
  179. private void updateLeadRow() {
  180. leadRow = getRowForPath(tree, tree.getLeadSelectionPath());
  181. }
  182. protected void paint(SynthContext context, Graphics g) {
  183. paintContext = context;
  184. updateLeadRow();
  185. Rectangle paintBounds = g.getClipBounds();
  186. Insets insets = tree.getInsets();
  187. TreePath initialPath = getClosestPathForLocation(tree, 0,
  188. paintBounds.y);
  189. Enumeration paintingEnumerator = treeState.getVisiblePathsFrom
  190. (initialPath);
  191. int row = treeState.getRowForPath(initialPath);
  192. int endY = paintBounds.y + paintBounds.height;
  193. TreeModel treeModel = tree.getModel();
  194. SynthContext cellContext = getContext(tree, Region.TREE_CELL);
  195. drawingCache.clear();
  196. setHashColor(context.getStyle().getColor(context,
  197. ColorType.FOREGROUND));
  198. if (paintingEnumerator != null) {
  199. // First pass, draw the rows
  200. boolean done = false;
  201. boolean isExpanded;
  202. boolean hasBeenExpanded;
  203. boolean isLeaf;
  204. Rectangle boundsBuffer = new Rectangle();
  205. Rectangle rowBounds = new Rectangle(0, 0, tree.getWidth(),0);
  206. Rectangle bounds;
  207. TreePath path;
  208. TreeCellRenderer renderer = tree.getCellRenderer();
  209. DefaultTreeCellRenderer dtcr = (renderer instanceof
  210. DefaultTreeCellRenderer) ? (DefaultTreeCellRenderer)
  211. renderer : null;
  212. configureRenderer(cellContext);
  213. while (!done && paintingEnumerator.hasMoreElements()) {
  214. path = (TreePath)paintingEnumerator.nextElement();
  215. if (path != null) {
  216. isLeaf = treeModel.isLeaf(path.getLastPathComponent());
  217. if (isLeaf) {
  218. isExpanded = hasBeenExpanded = false;
  219. }
  220. else {
  221. isExpanded = treeState.getExpandedState(path);
  222. hasBeenExpanded = tree.hasBeenExpanded(path);
  223. }
  224. bounds = treeState.getBounds(path, boundsBuffer);
  225. adjustCellBounds(tree, bounds, insets);
  226. rowBounds.y = bounds.y;
  227. rowBounds.height = bounds.height;
  228. paintRow(renderer, dtcr, context, cellContext, g,
  229. paintBounds, insets, bounds, rowBounds, path,
  230. row, isExpanded, hasBeenExpanded, isLeaf);
  231. if ((bounds.y + bounds.height) >= endY) {
  232. done = true;
  233. }
  234. }
  235. else {
  236. done = true;
  237. }
  238. row++;
  239. }
  240. // Draw the connecting lines and controls.
  241. // Find each parent and have them draw a line to their last child
  242. boolean rootVisible = tree.isRootVisible();
  243. TreePath parentPath = initialPath;
  244. parentPath = parentPath.getParentPath();
  245. while (parentPath != null) {
  246. paintVerticalPartOfLeg(g, paintBounds, insets, parentPath);
  247. drawingCache.put(parentPath, Boolean.TRUE);
  248. parentPath = parentPath.getParentPath();
  249. }
  250. done = false;
  251. paintingEnumerator = treeState.getVisiblePathsFrom(initialPath);
  252. while (!done && paintingEnumerator.hasMoreElements()) {
  253. path = (TreePath)paintingEnumerator.nextElement();
  254. if (path != null) {
  255. isLeaf = treeModel.isLeaf(path.getLastPathComponent());
  256. if (isLeaf) {
  257. isExpanded = hasBeenExpanded = false;
  258. }
  259. else {
  260. isExpanded = treeState.getExpandedState(path);
  261. hasBeenExpanded = tree.hasBeenExpanded(path);
  262. }
  263. bounds = treeState.getBounds(path, boundsBuffer);
  264. adjustCellBounds(tree, bounds, insets);
  265. // See if the vertical line to the parent has been drawn.
  266. parentPath = path.getParentPath();
  267. if (parentPath != null) {
  268. if (drawingCache.get(parentPath) == null) {
  269. paintVerticalPartOfLeg(g, paintBounds, insets,
  270. parentPath);
  271. drawingCache.put(parentPath, Boolean.TRUE);
  272. }
  273. paintHorizontalPartOfLeg(g,
  274. paintBounds, insets, bounds,
  275. path, row, isExpanded,
  276. hasBeenExpanded, isLeaf);
  277. }
  278. else if (rootVisible && row == 0) {
  279. paintHorizontalPartOfLeg(g,
  280. paintBounds, insets, bounds,
  281. path, row, isExpanded,
  282. hasBeenExpanded, isLeaf);
  283. }
  284. if (shouldPaintExpandControl(path, row, isExpanded,
  285. hasBeenExpanded, isLeaf)) {
  286. paintExpandControl(g, paintBounds,
  287. insets, bounds, path, row,
  288. isExpanded, hasBeenExpanded,isLeaf);
  289. }
  290. if ((bounds.y + bounds.height) >= endY) {
  291. done = true;
  292. }
  293. }
  294. else {
  295. done = true;
  296. }
  297. row++;
  298. }
  299. }
  300. cellContext.dispose();
  301. // Empty out the renderer pane, allowing renderers to be gc'ed.
  302. rendererPane.removeAll();
  303. }
  304. private void configureRenderer(SynthContext context) {
  305. TreeCellRenderer renderer = tree.getCellRenderer();
  306. if (renderer instanceof DefaultTreeCellRenderer) {
  307. DefaultTreeCellRenderer r = (DefaultTreeCellRenderer)renderer;
  308. SynthStyle style = context.getStyle();
  309. context.setComponentState(ENABLED | SELECTED);
  310. Color color = r.getTextSelectionColor();
  311. if (color == null || (color instanceof UIResource)) {
  312. r.setTextSelectionColor(style.getColor(
  313. context, ColorType.TEXT_FOREGROUND));
  314. }
  315. color = r.getBackgroundSelectionColor();
  316. if (color == null || (color instanceof UIResource)) {
  317. r.setBackgroundSelectionColor(style.getColor(
  318. context, ColorType.TEXT_BACKGROUND));
  319. }
  320. context.setComponentState(ENABLED);
  321. color = r.getTextNonSelectionColor();
  322. if (color == null || color instanceof UIResource) {
  323. r.setTextNonSelectionColor(style.getColor(
  324. context, ColorType.TEXT_FOREGROUND));
  325. }
  326. color = r.getBackgroundNonSelectionColor();
  327. if (color instanceof UIResource) {
  328. r.setBackgroundNonSelectionColor(style.getColor(
  329. context, ColorType.TEXT_BACKGROUND));
  330. }
  331. }
  332. }
  333. protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
  334. Insets insets, Rectangle bounds,
  335. TreePath path, int row,
  336. boolean isExpanded,
  337. boolean hasBeenExpanded, boolean
  338. isLeaf) {
  339. if (drawHorizontalLines) {
  340. super.paintHorizontalPartOfLeg(g, clipBounds, insets, bounds,
  341. path, row, isExpanded,
  342. hasBeenExpanded, isLeaf);
  343. }
  344. }
  345. protected void paintHorizontalLine(Graphics g, JComponent c, int y,
  346. int left, int right) {
  347. paintContext.getStyle().getGraphicsUtils(paintContext).drawLine(
  348. paintContext, "Tree.horizontalLine", g, left, y, right, y);
  349. }
  350. protected void paintVerticalPartOfLeg(Graphics g,
  351. Rectangle clipBounds, Insets insets,
  352. TreePath path) {
  353. if (drawVerticalLines) {
  354. super.paintVerticalPartOfLeg(g, clipBounds, insets, path);
  355. }
  356. }
  357. protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
  358. int bottom) {
  359. paintContext.getStyle().getGraphicsUtils(paintContext).drawLine(
  360. paintContext, "Tree.verticalLine", g, x, top, x, bottom);
  361. }
  362. protected void paintRow(TreeCellRenderer renderer,
  363. DefaultTreeCellRenderer dtcr, SynthContext treeContext,
  364. SynthContext cellContext, Graphics g, Rectangle clipBounds,
  365. Insets insets, Rectangle bounds, Rectangle rowBounds,
  366. TreePath path, int row, boolean isExpanded,
  367. boolean hasBeenExpanded, boolean isLeaf) {
  368. // Don't paint the renderer if editing this row.
  369. boolean selected = tree.isRowSelected(row);
  370. if (selected) {
  371. cellContext.setComponentState(ENABLED | SELECTED);
  372. }
  373. else {
  374. cellContext.setComponentState(ENABLED);
  375. }
  376. if (dtcr != null && (dtcr.getBorderSelectionColor() instanceof
  377. UIResource)) {
  378. dtcr.setBorderSelectionColor(style.getColor(
  379. cellContext, ColorType.FOCUS));
  380. }
  381. SynthLookAndFeel.updateSubregion(cellContext, g, rowBounds);
  382. cellContext.getPainter().paintTreeCellBackground(cellContext, g,
  383. rowBounds.x, rowBounds.y, rowBounds.width,
  384. rowBounds.height);
  385. cellContext.getPainter().paintTreeCellBorder(cellContext, g,
  386. rowBounds.x, rowBounds.y, rowBounds.width,
  387. rowBounds.height);
  388. if (editingComponent != null && editingRow == row) {
  389. return;
  390. }
  391. int leadIndex;
  392. if (tree.hasFocus()) {
  393. leadIndex = leadRow;
  394. }
  395. else {
  396. leadIndex = -1;
  397. }
  398. Component component = renderer.getTreeCellRendererComponent(
  399. tree, path.getLastPathComponent(),
  400. selected, isExpanded, isLeaf, row,
  401. (leadIndex == row));
  402. rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y,
  403. bounds.width, bounds.height, true);
  404. }
  405. protected void drawCentered(Component c, Graphics graphics, Icon icon,
  406. int x, int y) {
  407. int w = SynthIcon.getIconWidth(icon, paintContext);
  408. int h = SynthIcon.getIconHeight(icon, paintContext);
  409. SynthIcon.paintIcon(icon, paintContext, graphics, x - w2, y - h2, w,
  410. h);
  411. }
  412. public void propertyChange(PropertyChangeEvent event) {
  413. if (SynthLookAndFeel.shouldUpdateStyle(event)) {
  414. updateStyle((JTree)event.getSource());
  415. }
  416. }
  417. protected int getRowX(int row, int depth) {
  418. return super.getRowX(row, depth) + padding;
  419. }
  420. private class SynthTreeCellRenderer extends DefaultTreeCellRenderer
  421. implements UIResource {
  422. SynthTreeCellRenderer() {
  423. }
  424. public String getName() {
  425. return "Tree.cellRenderer";
  426. }
  427. public Component getTreeCellRendererComponent(JTree tree, Object value,
  428. boolean sel,
  429. boolean expanded,
  430. boolean leaf, int row,
  431. boolean hasFocus) {
  432. if (!useTreeColors && (sel || hasFocus)) {
  433. SynthLookAndFeel.setSelectedUI((SynthLabelUI)SynthLookAndFeel.
  434. getUIOfType(getUI(), SynthLabelUI.class),
  435. sel, hasFocus, tree.isEnabled());
  436. }
  437. else {
  438. SynthLookAndFeel.resetSelectedUI();
  439. }
  440. return super.getTreeCellRendererComponent(tree, value, sel,
  441. expanded, leaf, row, hasFocus);
  442. }
  443. public void paint(Graphics g) {
  444. paintComponent(g);
  445. if (hasFocus) {
  446. SynthContext context = getContext(tree, Region.TREE_CELL);
  447. if (context.getStyle() == null) {
  448. assert false: "SynthTreeCellRenderer is being used " +
  449. "outside of UI that created it";
  450. return;
  451. }
  452. int imageOffset = 0;
  453. Icon currentI = getIcon();
  454. if(currentI != null && getText() != null) {
  455. imageOffset = currentI.getIconWidth() +
  456. Math.max(0, getIconTextGap() - 1);
  457. }
  458. if (selected) {
  459. context.setComponentState(ENABLED | SELECTED);
  460. }
  461. else {
  462. context.setComponentState(ENABLED);
  463. }
  464. if(getComponentOrientation().isLeftToRight()) {
  465. context.getPainter().paintTreeCellFocus(context, g,
  466. imageOffset, 0, getWidth() - imageOffset,
  467. getHeight());
  468. }
  469. else {
  470. context.getPainter().paintTreeCellFocus(context, g,
  471. 0, 0, getWidth() - imageOffset, getHeight());
  472. }
  473. context.dispose();
  474. }
  475. SynthLookAndFeel.resetSelectedUI();
  476. }
  477. }
  478. private static class SynthTreeCellEditor extends DefaultTreeCellEditor {
  479. public SynthTreeCellEditor(JTree tree,
  480. DefaultTreeCellRenderer renderer) {
  481. super(tree, renderer);
  482. setBorderSelectionColor(null);
  483. }
  484. protected TreeCellEditor createTreeCellEditor() {
  485. JTextField tf = new JTextField() {
  486. public String getName() {
  487. return "Tree.cellEditor";
  488. }
  489. };
  490. DefaultCellEditor editor = new DefaultCellEditor(tf);
  491. // One click to edit.
  492. editor.setClickCountToStart(1);
  493. return editor;
  494. }
  495. }
  496. //
  497. // BasicTreeUI directly uses expandIcon outside of the Synth methods.
  498. // To get the correct context we return an instance of this that fetches
  499. // the SynthContext as needed.
  500. //
  501. private class ExpandedIconWrapper extends SynthIcon {
  502. public void paintIcon(SynthContext context, Graphics g, int x,
  503. int y, int w, int h) {
  504. if (context == null) {
  505. context = getContext(tree);
  506. SynthIcon.paintIcon(expandedIcon, context, g, x, y, w, h);
  507. context.dispose();
  508. }
  509. else {
  510. SynthIcon.paintIcon(expandedIcon, context, g, x, y, w, h);
  511. }
  512. }
  513. public int getIconWidth(SynthContext context) {
  514. int width;
  515. if (context == null) {
  516. context = getContext(tree);
  517. width = SynthIcon.getIconWidth(expandedIcon, context);
  518. context.dispose();
  519. }
  520. else {
  521. width = SynthIcon.getIconWidth(expandedIcon, context);
  522. }
  523. return width;
  524. }
  525. public int getIconHeight(SynthContext context) {
  526. int height;
  527. if (context == null) {
  528. context = getContext(tree);
  529. height = SynthIcon.getIconHeight(expandedIcon, context);
  530. context.dispose();
  531. }
  532. else {
  533. height = SynthIcon.getIconHeight(expandedIcon, context);
  534. }
  535. return height;
  536. }
  537. }
  538. }