001: /*
002: * Javu WingS - Lightweight Java Component Set
003: * Copyright (c) 2005-2007 Krzysztof A. Sadlocha
004: * e-mail: ksadlocha@programics.com
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020:
021: package com.javujavu.javux.wings;
022:
023: import java.awt.AWTEvent;
024: import java.awt.Color;
025: import java.awt.Dimension;
026: import java.awt.Graphics;
027: import java.awt.ItemSelectable;
028: import java.awt.Point;
029: import java.awt.Rectangle;
030: import java.awt.event.FocusEvent;
031: import java.awt.event.ItemEvent;
032: import java.awt.event.KeyEvent;
033: import java.awt.event.MouseEvent;
034: import java.util.Vector;
035: import com.javujavu.javux.wings.item.ItemEditor;
036: import com.javujavu.javux.wings.item.ItemOwner;
037: import com.javujavu.javux.wings.item.ItemRenderer;
038: import com.javujavu.javux.wings.item.ItemStyles;
039: import com.javujavu.javux.wings.tree.TreeNode;
040:
041: /**
042: * This class displays a hierarchical tree of items<br>
043: * <br>
044: * <b>This class is thread safe.</b>
045: **/
046: public class WingTree extends WingComponent implements ItemSelectable,
047: ItemOwner {
048: protected ItemStyles itemStyles;
049: private WingImage imgExpanded;
050: private WingImage imgCollapsed;
051: private Color colLine;
052:
053: private TreeNode root;
054: private boolean rootVisible;
055: private/*final*/Vector visible;
056: private Rectangle[] itemBounds;
057: private int rowWidth;
058:
059: private boolean focused;
060: private boolean mouseDown;
061:
062: private TreeNode selectedNode;
063: private ItemEditor itemEditor;
064: private ItemRenderer itemRenderer;
065: private WingComponent editor;
066: private TreeNode editNode;
067:
068: public WingTree() {
069: this (new TreeNode("root"));
070: }
071:
072: public WingTree(TreeNode root) {
073: visible = new Vector();
074:
075: setRoot(root);
076: setWingFocusable(true);
077:
078: enableEvents(AWTEvent.MOUSE_EVENT_MASK
079: | AWTEvent.MOUSE_MOTION_EVENT_MASK
080: | AWTEvent.FOCUS_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
081: }
082:
083: public void loadSkin() {
084: String idt = WingSkin.catKeys(styleId, "tree");
085:
086: stNormal = WingSkin.getStyle(idt, null, DOC | NORMAL, null);
087: stDisabled = WingSkin.getStyle(idt, null, DOC | DISABLED,
088: stNormal).merge(stTop);
089: stNormal = stNormal.merge(stTop);
090: itemStyles = new ItemStyles(idt, stTop);
091: imgExpanded = WingSkin.getImage(idt, "expanded");
092: imgCollapsed = WingSkin.getImage(idt, "collapsed");
093: colLine = WingSkin.getColor(idt, "line");
094: }
095:
096: public void setItemRenderer(ItemRenderer itemRenderer) {
097: this .itemRenderer = itemRenderer;
098: revalidateAndRepaint();
099: }
100:
101: public ItemRenderer getItemRenderer() {
102: ItemRenderer r = itemRenderer;
103: if (r == null)
104: r = getRenderer();
105: return r;
106: }
107:
108: public void setEditor(ItemEditor editor) {
109: this .itemEditor = editor;
110: }
111:
112: public boolean isEditable() {
113: return itemEditor != null;
114: }
115:
116: public synchronized void setRoot(TreeNode r) {
117: root = r;
118: if (!rootVisible)
119: root.setExpanded(true);
120: selectedNode = null;
121: revalidateAndRepaint();
122: }
123:
124: public TreeNode getRoot() {
125: return root;
126: }
127:
128: public synchronized void setRootVisible(boolean rootVisible) {
129: this .rootVisible = rootVisible;
130: if (!rootVisible) {
131: root.setExpanded(true);
132: }
133: revalidateAndRepaint();
134: }
135:
136: public synchronized void expandNode(TreeNode n) {
137: n.setExpanded(true);
138: if (isVisible(n))
139: revalidateAndRepaint();
140: }
141:
142: public synchronized void collapseNode(TreeNode n) {
143: n.setExpanded(false);
144: if (isVisible(n)) {
145: if (selectedNode != null && selectedNode.isChildOf(n)) {
146: selectedNode = n;
147: }
148: revalidateAndRepaint();
149: }
150: }
151:
152: public boolean isVisible(TreeNode n) {
153: if (n == null)
154: return false;
155: return visible.contains(n);
156: }
157:
158: public void addNode(TreeNode n, TreeNode parent) {
159: insertNode(n, parent, -1);
160: }
161:
162: public synchronized void insertNode(TreeNode n, TreeNode parent,
163: int index) {
164: removeNode(n);
165: if (parent == null)
166: parent = root;
167: int pcc = parent.getChildCount();
168: if (index < 0 || index > pcc)
169: index = pcc;
170: parent.insert(n, index);
171: if (parent.isExpanded())
172: revalidateAndRepaint();
173: else if (isVisible(parent))
174: repaint();
175: }
176:
177: public synchronized void removeNode(TreeNode n) {
178: TreeNode parent = n.getParent();
179: if (parent == null)
180: return;
181: if (isVisible(parent) || parent == root) {
182: if (parent.isExpanded())
183: revalidateAndRepaint();
184: else
185: repaint();
186:
187: if (selectedNode == n) {
188: int cc = parent.getChildCount();
189: if (cc > 1) {
190: int i = parent.indexOf(n);
191: selectedNode = parent.childAt(i
192: + ((i + 1 < cc) ? +1 : -1));
193: } else if (parent != root)
194: selectedNode = parent;
195: else
196: selectedNode = null;
197: }
198: }
199: parent.remove(n);
200: }
201:
202: public synchronized void makeVisible(TreeNode node) {
203: TreeNode parent;
204: while ((parent = node.getParent()) != null) {
205: node = parent;
206: node.setExpanded(true);
207: }
208: revalidateAndRepaint();
209: }
210:
211: public TreeNode getSelected() {
212: return selectedNode;
213: }
214:
215: /**
216: * <br><br><strong>This method acquire TreeLock</strong>
217: */
218: public void setSelected(TreeNode n) {
219: synchronized (getTreeLock()) {
220: synchronized (this ) {
221: if (n == selectedNode)
222: return;
223: selectedNode = n;
224: repaint();
225:
226: if (n != null) {
227: if (!isVisible(n))
228: makeVisible(n);
229: scrollRectToVisible(getItemRect(n));
230: }
231: }
232: }
233: }
234:
235: /**
236: * <br><br><strong>This method acquire TreeLock</strong>
237: */
238: public Rectangle getItemRect(TreeNode n) {
239: synchronized (getTreeLock()) {
240: synchronized (this ) {
241: wingValidate();
242: if (!isVisible(n))
243: return null;
244: Rectangle[] itemBounds = getItemBounds();
245: return itemBounds[visible.indexOf(n)];
246: }
247: }
248: }
249:
250: ///////////////////////////////////////////////////////////
251: // editor support
252:
253: public WingComponent editNode(TreeNode node) {
254: if (!isEditable() || !isEnabled())
255: return null;
256:
257: synchronized (getTreeLock()) {
258: synchronized (this ) {
259: cleanEditor(true);
260:
261: if (node == null || node == root)
262: return null;
263: editNode = node;
264: editor = itemEditor.getComponent(this , node.getItem(),
265: node);
266: if (editor != null) {
267: makeVisible(node);
268: Rectangle b = getItemRect(node);
269: if (b != null) {
270: add(editor);
271: Point location = getLocation();
272: Dimension viewSize = getViewOrSize();
273: scrollRectToVisible(b);
274: b.width = 1;
275: scrollRectToVisible(b);
276: b.width = viewSize.width - b.x;
277: if (location.x < 0)
278: b.width += -location.x;
279: editor.setBounds(b);
280: editor.wingRequestFocusInWindow();
281: } else {
282: editor = null;
283: editNode = null;
284: }
285: } else
286: editNode = null;
287:
288: return editor;
289: }
290: }
291: }
292:
293: public synchronized void commitEdit(Object value) {
294: if (editNode != null) {
295: editNode.setItem(value);
296: revalidateAndRepaint();
297: removeEditor();
298: wingRequestFocusInWindow();
299: }
300: }
301:
302: public synchronized void removeEditor() {
303: if (editor != null) {
304: editNode = null;
305: revalidateAndRepaint();
306: }
307: }
308:
309: /**
310: * <br><br><strong>This method acquire TreeLock</strong>
311: */
312: public void invalidate() {
313: cleanEditor(false);
314: super .invalidate();
315: }
316:
317: /**
318: * <br><br><strong>This method acquire TreeLock</strong>
319: */
320: private void cleanEditor(boolean unconditionally) {
321: synchronized (getTreeLock()) {
322: synchronized (this ) {
323: if (editor != null
324: && (unconditionally || editNode == null)) {
325: WingComponent toRemove = editor;
326: editor = null;
327: editNode = null;
328: remove(toRemove);
329: repaint();
330: }
331: }
332: }
333: }
334:
335: ///////////////////////////////////////////////////////////
336:
337: public void revalidateAndRepaint() {
338: itemBounds = null;
339: super .revalidateAndRepaint();
340: }
341:
342: protected synchronized Rectangle[] getItemBounds() {
343: getPreferredSize();
344: return itemBounds;
345: }
346:
347: public synchronized Dimension getPreferredSize() {
348: Dimension prefSize = wingPrefSize;
349: if (prefSize == null || itemBounds == null) {
350: visible.removeAllElements();
351: if (root != null) {
352: findVisible(root);
353: if (!rootVisible && visible.size() > 0)
354: visible.removeElementAt(0);
355: }
356:
357: if (itemBounds == null
358: || itemBounds.length != visible.size()) {
359: itemBounds = new Rectangle[visible.size()];
360: }
361:
362: TreeNode n;
363: Dimension di;
364: int w;
365: int indent = imgExpanded.getWidth();
366: int expHeight = imgExpanded.getHeight()
367: + itemStyles.normal.margin.top
368: + itemStyles.normal.margin.bottom;
369: ItemRenderer renderer = getItemRenderer();
370: rowWidth = 1;
371: int h = 0;
372: for (int i = 0; i < visible.size(); i++) {
373: n = (TreeNode) visible.elementAt(i);
374: w = (n.getLevel() + ((rootVisible) ? 1 : 0)) * indent
375: + 2 * stNormal.gap;
376: di = renderer.getItemSize(n.getItem(), this ,
377: itemStyles.normal, n);
378: if (itemBounds[i] == null)
379: itemBounds[i] = new Rectangle();
380: itemBounds[i].x = w;
381: itemBounds[i].y = h;
382: w += itemBounds[i].width = di.width;
383: h += itemBounds[i].height = (di.height > expHeight) ? di.height
384: : expHeight;
385: if (w > rowWidth)
386: rowWidth = w;
387: }
388:
389: if (!isVisible(selectedNode))
390: selectedNode = null;
391:
392: wingPrefSize = prefSize = new Dimension(rowWidth, h);
393: }
394: return prefSize;
395: }
396:
397: private void findVisible(TreeNode n) {
398: visible.addElement(n);
399: if (n.isExpanded()) {
400: for (int i = 0; i < n.getChildCount(); i++) {
401: findVisible((TreeNode) n.childAt(i));
402: }
403: }
404: }
405:
406: public synchronized void getScrollIncrements(Point unit, Point block) {
407: Rectangle[] itemBounds = getItemBounds();
408:
409: int height = (itemBounds.length > 0) ? itemBounds[0].height
410: : 10;
411: int width = (rowWidth > 0) ? rowWidth / 3 : 10;
412: unit.x = width;
413: unit.y = height;
414: Dimension viewSize = getViewOrSize();
415: block.x = viewSize.width - width / 3;
416: block.y = (viewSize.height / height - 1) * height;
417: }
418:
419: public synchronized void wingPaint(Graphics g) {
420: Rectangle[] itemBounds = getItemBounds();
421:
422: Rectangle cb = g.getClipBounds();
423: int y0 = (cb != null) ? cb.y : 0;
424: int ye = (cb != null) ? cb.y + cb.height : getSize().height;
425: TreeNode node;
426: int x, y, i, y2, indent = imgExpanded.getWidth();
427: int i0 = 0, ie;
428: for (i = 0; i < itemBounds.length; i++) {
429: if (itemBounds[i].y >= ye)
430: break;
431: if (itemBounds[i].y <= y0)
432: i0 = i;
433: }
434: ie = i - 1;
435:
436: if (ie >= visible.size())
437: ie = visible.size() - 1;
438:
439: if (colLine != null
440: && i0 <= ie
441: && (node = ((TreeNode) visible.elementAt(i0))
442: .getParent()) != null) {
443: g.setColor(colLine);
444: while (node != null) {
445: if (node == root && !rootVisible) {
446: y = 0;
447: x = stNormal.gap + indent / 2;
448: } else {
449: Rectangle b = itemBounds[visible.indexOf(node)];
450: y = b.y + b.height;
451: x = b.x - stNormal.gap + indent / 2;
452: }
453: Rectangle b = itemBounds[visible.indexOf(node
454: .childAt(node.getChildCount() - 1))];
455: y2 = b.y + b.height / 2 - 1;
456: g.drawLine(x, y, x, y2);
457: node = node.getParent();
458: }
459: }
460: ItemRenderer renderer = getItemRenderer();
461: for (i = i0; i <= ie; i++) {
462: node = (TreeNode) visible.elementAt(i);
463: Rectangle b = itemBounds[i];
464: x = b.x - stNormal.gap - indent;
465: y = b.y;
466: if (colLine != null) {
467: g.setColor(colLine);
468: y2 = y + b.height / 2 - 1;
469: g.drawLine(x + indent / 2, y2, x + indent
470: + stNormal.gap - 1, y2);
471: if (!node.isLeaf() && node.isExpanded()) {
472: int x2 = x + indent + indent / 2;
473: Rectangle b2 = itemBounds[visible.indexOf(node
474: .childAt(node.getChildCount() - 1))];
475: y2 = b2.y + b2.height / 2 - 1;
476: g.drawLine(x2, y + b.height, x2, y2);
477: }
478: }
479: if (!node.isLeaf()) {
480: WingImage img = (node.isExpanded()) ? imgExpanded
481: : imgCollapsed;
482: if (img != null) {
483: y2 = y + (b.height - img.getHeight()) / 2;
484: img.drawImage(g, x, y2, this );
485: }
486: }
487: boolean selected = node == selectedNode;
488: boolean focused = selected && this .focused;
489: Style st = itemStyles.get(isEnabled(), selected, focused,
490: false);
491:
492: renderer.drawItem(g, b.x, b.y, b.width, b.height, node
493: .getItem(), this , st, st.margin, LEFT, RIGHT, node);
494: }
495: }
496:
497: protected void wingProcessMouseEvent(MouseEvent e) {
498: int id = e.getID();
499: if (isEnabled()) {
500: if (id == MouseEvent.MOUSE_PRESSED) {
501: mouseDown = true;
502: mouseAction(e.getPoint(), true, false, false);
503: if (wingFocusable && !focused)
504: requestFocus(); //wingRequestFocusInWindow();
505: } else if (id == MouseEvent.MOUSE_RELEASED) {
506: mouseDown = false;
507: } else if (id == MouseEvent.MOUSE_CLICKED) {
508: if (e.getClickCount() == 2) {
509: mouseAction(e.getPoint(), false, true, false);
510: }
511: } else if (id == MouseEvent.MOUSE_DRAGGED) {
512: if (mouseDown)
513: mouseAction(e.getPoint(), false, false, true);
514: }
515: }
516: }
517:
518: private void mouseAction(Point p, boolean press, boolean dbclick,
519: boolean drag) {
520: TreeNode notifyNode = null;
521:
522: synchronized (getTreeLock()) {
523: synchronized (this ) {
524: Rectangle[] itemBounds = getItemBounds();
525: TreeNode oldSel = selectedNode;
526: int visIndex;
527: for (visIndex = 0; visIndex < itemBounds.length; visIndex++) {
528: if (p.y < itemBounds[visIndex].y) {
529: visIndex--;
530: break;
531: }
532: if (p.y < itemBounds[visIndex].y
533: + itemBounds[visIndex].height) {
534: break;
535: }
536: }
537: if (visIndex < 0 || visIndex >= visible.size()) {
538: if (drag) {
539: if (visIndex < 0)
540: visIndex = 0;
541: else
542: visIndex = visible.size() - 1;
543: if (visIndex < 0)
544: return;
545: } else
546: return;
547: }
548: TreeNode n = (TreeNode) visible.elementAt(visIndex);
549: if (drag) {
550: setSelected(n);
551: } else {
552: int x = itemBounds[visIndex].x;
553: int indent = imgExpanded.getWidth() + 2
554: * stNormal.gap;
555: if (p.x < x - indent)
556: return;
557: if (p.x < x) {
558: if (!n.isLeaf() && !dbclick) {
559: if (n.isExpanded())
560: collapseNode(n);
561: else
562: expandNode(n);
563: mouseDown = false;
564: }
565: } else {
566: if (press) {
567: setSelected(n);
568: } else if (dbclick) {
569: if (isEditable())
570: editNode(selectedNode);
571: else if (n.isExpanded())
572: collapseNode(n);
573: else
574: expandNode(n);
575: }
576: }
577: }
578: if (selectedNode != oldSel)
579: notifyNode = selectedNode;
580: }
581: }
582: if (notifyNode != null) {
583: wingProcessItemEvent(new ItemEvent(this ,
584: ItemEvent.ITEM_STATE_CHANGED, notifyNode,
585: ItemEvent.SELECTED));
586: }
587: }
588:
589: protected void wingProcessKeyEvent(KeyEvent e,
590: WingComponent redirect) {
591: if (e.getID() == KeyEvent.KEY_PRESSED && e.getModifiers() == 0) {
592: int key = e.getKeyCode();
593: boolean consume = true;
594:
595: TreeNode notifyNode = null;
596:
597: synchronized (getTreeLock()) {
598: synchronized (this ) {
599: TreeNode oldSel = selectedNode;
600:
601: if (key == KeyEvent.VK_UP
602: || key == KeyEvent.VK_DOWN) {
603: int visIndex = (selectedNode != null) ? visible
604: .indexOf(selectedNode) : -1;
605: if (visIndex == -1)
606: visIndex = 0;
607: if (key == KeyEvent.VK_UP)
608: visIndex--;
609: else
610: visIndex++;
611: if (visIndex < 0)
612: visIndex = 0;
613: if (visIndex >= visible.size())
614: visIndex = visible.size() - 1;
615: if (visIndex != -1) {
616: setSelected((TreeNode) visible
617: .elementAt(visIndex));
618: }
619: } else if ((key == KeyEvent.VK_ENTER || key == KeyEvent.VK_F2)
620: && itemEditor != null) {
621: editNode(selectedNode);
622: } else if ((key == KeyEvent.VK_ENTER || key == KeyEvent.VK_LEFT)
623: && selectedNode != null
624: && selectedNode.isExpanded()) {
625: collapseNode(selectedNode);
626: } else if (key == KeyEvent.VK_RIGHT
627: && selectedNode != null
628: && !selectedNode.isExpanded()) {
629: expandNode(selectedNode);
630: } else
631: consume = false;
632:
633: if (selectedNode != oldSel)
634: notifyNode = selectedNode;
635: }
636: }
637: if (notifyNode != null) {
638: wingProcessItemEvent(new ItemEvent(this ,
639: ItemEvent.ITEM_STATE_CHANGED, notifyNode,
640: ItemEvent.SELECTED));
641: }
642:
643: if (consume)
644: e.consume();
645: }
646: }
647:
648: protected void processFocusEvent(FocusEvent e) {
649: int id = e.getID();
650: if (id == FocusEvent.FOCUS_GAINED) {
651: focused = true;
652: if (selectedNode == null) {
653: TreeNode n = null;
654: synchronized (this ) {
655: if (visible.size() > 0)
656: n = (TreeNode) visible.elementAt(0);
657: }
658: if (n != null) {
659: setSelected(n);
660: wingProcessItemEvent(new ItemEvent(this ,
661: ItemEvent.ITEM_STATE_CHANGED, n,
662: ItemEvent.SELECTED));
663: // postOnEventThread(new ItemEvent(
664: // this, ItemEvent.ITEM_STATE_CHANGED,
665: // n, ItemEvent.SELECTED));
666: }
667: }
668: repaint();
669: } else if (id == FocusEvent.FOCUS_LOST) {
670: focused = false;
671: repaint();
672: }
673: super .processFocusEvent(e);
674: }
675:
676: public Object[] getSelectedObjects() {
677: TreeNode n = getSelected();
678: Object[] r;
679: if (n != null) {
680: r = new Object[1];
681: r[0] = n;
682: } else
683: r = new Object[0];
684: return r;
685: }
686: }
|