001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Anton Avtamonov
019: * @version $Revision$
020: */package javax.swing.tree;
021:
022: import java.awt.Rectangle;
023: import java.util.Enumeration;
024: import java.util.Iterator;
025: import java.util.LinkedList;
026: import java.util.List;
027: import java.util.NoSuchElementException;
028:
029: import javax.swing.event.TreeModelEvent;
030:
031: public abstract class AbstractLayoutCache implements RowMapper {
032: public abstract static class NodeDimensions {
033: public abstract Rectangle getNodeDimensions(Object value,
034: int row, int depth, boolean expanded, Rectangle bounds);
035: }
036:
037: class StateNode {
038: private final StateNode parent;
039: private final TreePath path;
040: private final List children = new LinkedList();
041: private boolean expanded = true;
042: protected int totalChildrenCount;
043: private Object[] modelChildren;
044:
045: private boolean isValid;
046:
047: public StateNode(final StateNode parent, final TreePath path) {
048: this .parent = parent;
049: this .path = path;
050: }
051:
052: public boolean isRoot() {
053: return getParent() == null;
054: }
055:
056: public void add(final StateNode child, final int index) {
057: synchronized (AbstractLayoutCache.this ) {
058: children.add(index, child);
059: child.invalidateTreePartBelow();
060: }
061: }
062:
063: public void add(final StateNode child) {
064: synchronized (AbstractLayoutCache.this ) {
065: add(child, children.size());
066: child.invalidateTreePartBelow();
067: }
068: }
069:
070: public void remove(final int index) {
071: synchronized (AbstractLayoutCache.this ) {
072: children.remove(index);
073: invalidateTreePartBelow();
074: }
075: }
076:
077: public void remove(final StateNode child) {
078: synchronized (AbstractLayoutCache.this ) {
079: children.remove(child);
080: invalidateTreePartBelow();
081: }
082: }
083:
084: public void removeAll() {
085: synchronized (AbstractLayoutCache.this ) {
086: children.clear();
087: invalidateTreePartBelow();
088: }
089: }
090:
091: public StateNode getParent() {
092: return parent;
093: }
094:
095: public Object getModelNode() {
096: return path.getLastPathComponent();
097: }
098:
099: public TreePath getModelPath() {
100: return path;
101: }
102:
103: public Object getModelChildNode(final int index) {
104: return getModelChildren()[index];
105: }
106:
107: public int getModelChildCount() {
108: return getModelChildren().length;
109: }
110:
111: public int getModelIndexOfChild(final Object modelChild) {
112: if (modelChild == null) {
113: return -1;
114: }
115:
116: Object[] mChildren = getModelChildren();
117: for (int i = 0; i < mChildren.length; i++) {
118: if (modelChild.equals(mChildren[i])) {
119: return i;
120: }
121: }
122: return -1;
123: }
124:
125: public Object[] getModelChildren() {
126: if (modelChildren == null) {
127: int childCount = getModel().getChildCount(
128: getModelNode());
129: modelChildren = new Object[childCount];
130: for (int i = 0; i < childCount; i++) {
131: modelChildren[i] = getModel().getChild(
132: getModelNode(), i);
133: }
134: }
135: return modelChildren;
136: }
137:
138: public int getChildCount() {
139: synchronized (AbstractLayoutCache.this ) {
140: return unsyncGetChildCount();
141: }
142: }
143:
144: public boolean isLeaf() {
145: synchronized (AbstractLayoutCache.this ) {
146: return unsyncIsLeaf();
147: }
148: }
149:
150: public void setExpanded() {
151: expanded = true;
152: invalidateTreePartBelow();
153: }
154:
155: public void setCollapsed() {
156: expanded = false;
157: invalidateTreePartBelow();
158: }
159:
160: public boolean isExpanded() {
161: return expanded;
162: }
163:
164: public List children() {
165: synchronized (AbstractLayoutCache.this ) {
166: return children;
167: }
168: }
169:
170: public StateNode get(final int index) {
171: synchronized (AbstractLayoutCache.this ) {
172: return unsyncGet(index);
173: }
174: }
175:
176: public StateNode getChild(final Object modelNode) {
177: synchronized (AbstractLayoutCache.this ) {
178: for (Iterator it = children.iterator(); it.hasNext();) {
179: StateNode stateNode = (StateNode) it.next();
180: if (stateNode.getModelNode().equals(modelNode)) {
181: return stateNode;
182: }
183: }
184:
185: return null;
186: }
187: }
188:
189: public StateNode addChild(final Object modelChildNode) {
190: synchronized (AbstractLayoutCache.this ) {
191: int childModelIndex = getModelIndexOfChild(modelChildNode);
192: int insertionIndex;
193: for (insertionIndex = 0; insertionIndex < children
194: .size(); insertionIndex++) {
195: StateNode childStateNode = (StateNode) children
196: .get(insertionIndex);
197: int childModelIndexForStateNode = getModelIndexOfChild(childStateNode
198: .getModelNode());
199: if (childModelIndexForStateNode == childModelIndex) {
200: return childStateNode;
201: }
202: if (childModelIndexForStateNode > childModelIndex) {
203: break;
204: }
205: }
206: StateNode newChildStateNode = createStateNode(this ,
207: getModelPath()
208: .pathByAddingChild(modelChildNode));
209: add(newChildStateNode, insertionIndex);
210:
211: return newChildStateNode;
212: }
213: }
214:
215: public StateNode getNextSibling() {
216: synchronized (AbstractLayoutCache.this ) {
217: if (getParent() == null) {
218: return null;
219: }
220: int index = getParent().children().indexOf(this );
221: return index + 1 < getParent().getChildCount() ? getParent()
222: .get(index + 1)
223: : null;
224: }
225: }
226:
227: public int getTotalChildrenCount() {
228: validate();
229: return totalChildrenCount;
230: }
231:
232: public boolean isValid() {
233: return isValid;
234: }
235:
236: public void validate() {
237: synchronized (AbstractLayoutCache.this ) {
238: if (isValid) {
239: return;
240: }
241:
242: // validation should go hierarchically, from the top to leaves
243: // therefore a parent shoul dbe validated first
244: if (getParent() != null) {
245: getParent().validate();
246: }
247:
248: // validation of the parent can cause children validation.
249: // In this case we don't need to validate this children again
250: if (isValid) {
251: return;
252: }
253:
254: // important to be BEFORE validateData() to get rid of potential cycling
255: isValid = true;
256: validateData();
257: }
258: }
259:
260: public String toString() {
261: return getModelNode().toString();
262: }
263:
264: public void invalidate() {
265: synchronized (AbstractLayoutCache.this ) {
266: isValid = false;
267: resetCachedData();
268: if (parent != null) {
269: parent.invalidate();
270: }
271: }
272: }
273:
274: public void invalidateSubtree() {
275: invalidate();
276: synchronized (AbstractLayoutCache.this ) {
277: for (int i = 0; i < unsyncGetChildCount(); i++) {
278: unsyncInvalidateSubtree(unsyncGet(i));
279: }
280: }
281: }
282:
283: public void invalidateTreePartBelow() {
284: synchronized (AbstractLayoutCache.this ) {
285: unsyncInvalidateTreePartBelow(this );
286: }
287: }
288:
289: protected void validateData() {
290: }
291:
292: private void unsyncInvalidateTreePartBelow(final StateNode node) {
293: node.invalidate();
294:
295: if (!node.unsyncIsLeaf()) {
296: unsyncInvalidateTreePartBelow(node.unsyncGet(0));
297: } else {
298: StateNode currentNode = node;
299: while (currentNode != null) {
300: StateNode sibling = currentNode.getNextSibling();
301: if (sibling != null) {
302: unsyncInvalidateTreePartBelow(sibling);
303: break;
304: }
305: currentNode = currentNode.getParent();
306: }
307: }
308: }
309:
310: private boolean unsyncIsLeaf() {
311: return children.size() == 0;
312: }
313:
314: private void resetCachedData() {
315: modelChildren = null;
316: }
317:
318: private void unsyncInvalidateSubtree(final StateNode root) {
319: root.invalidate();
320: for (int i = 0; i < root.unsyncGetChildCount(); i++) {
321: unsyncInvalidateSubtree(root.unsyncGet(i));
322: }
323: }
324:
325: private StateNode unsyncGet(final int index) {
326: return (StateNode) children.get(index);
327: }
328:
329: private int unsyncGetChildCount() {
330: return children.size();
331: }
332: }
333:
334: protected NodeDimensions nodeDimensions;
335: protected TreeModel treeModel;
336: protected TreeSelectionModel treeSelectionModel;
337: protected boolean rootVisible;
338: protected int rowHeight;
339:
340: private StateNode stateRoot;
341:
342: public abstract boolean isExpanded(final TreePath path);
343:
344: public abstract Rectangle getBounds(TreePath path, Rectangle placeIn);
345:
346: public abstract TreePath getPathForRow(int row);
347:
348: public abstract int getRowForPath(TreePath path);
349:
350: public abstract TreePath getPathClosestTo(int x, int y);
351:
352: public abstract Enumeration<TreePath> getVisiblePathsFrom(
353: TreePath path);
354:
355: public abstract int getVisibleChildCount(TreePath path);
356:
357: public abstract void setExpandedState(TreePath path,
358: boolean isExpanded);
359:
360: public abstract boolean getExpandedState(TreePath path);
361:
362: public abstract int getRowCount();
363:
364: public abstract void invalidateSizes();
365:
366: public abstract void invalidatePathBounds(TreePath path);
367:
368: public abstract void treeNodesChanged(TreeModelEvent e);
369:
370: public abstract void treeNodesInserted(TreeModelEvent e);
371:
372: public abstract void treeNodesRemoved(TreeModelEvent e);
373:
374: public abstract void treeStructureChanged(TreeModelEvent e);
375:
376: public void setNodeDimensions(final NodeDimensions nd) {
377: nodeDimensions = nd;
378: }
379:
380: public NodeDimensions getNodeDimensions() {
381: return nodeDimensions;
382: }
383:
384: public void setModel(final TreeModel model) {
385: treeModel = model;
386: resetRoot(model);
387: }
388:
389: public TreeModel getModel() {
390: return treeModel;
391: }
392:
393: public void setRootVisible(final boolean rootVisible) {
394: this .rootVisible = rootVisible;
395: }
396:
397: public boolean isRootVisible() {
398: return rootVisible;
399: }
400:
401: public void setRowHeight(final int rowHeight) {
402: this .rowHeight = rowHeight;
403: }
404:
405: public int getRowHeight() {
406: return rowHeight;
407: }
408:
409: public void setSelectionModel(
410: final TreeSelectionModel selectionModel) {
411: if (treeSelectionModel != null) {
412: treeSelectionModel.setRowMapper(null);
413: }
414: treeSelectionModel = selectionModel;
415: if (treeSelectionModel != null) {
416: treeSelectionModel.setRowMapper(this );
417: }
418: }
419:
420: public TreeSelectionModel getSelectionModel() {
421: return treeSelectionModel;
422: }
423:
424: public int getPreferredHeight() {
425: return getRowCount() * rowHeight;
426: }
427:
428: public int getPreferredWidth(final Rectangle bounds) {
429: if (getNodeDimensions() == null || getStateRoot() == null) {
430: return 0;
431: }
432:
433: TreePath startPath;
434: TreePath endPath;
435: if (bounds != null) {
436: startPath = getPathClosestTo(bounds.x, bounds.y);
437: endPath = getPathClosestTo(bounds.x, bounds.y
438: + bounds.height - 1);
439: } else {
440: startPath = getStateRoot().getModelPath();
441: endPath = null;
442: }
443:
444: Enumeration paths = getVisiblePathsFromImpl(startPath);
445: if (paths == null) {
446: return 0;
447: }
448:
449: int result = 0;
450: while (paths.hasMoreElements()) {
451: TreePath path = (TreePath) paths.nextElement();
452:
453: if (isRoot(path) && !isRootVisible()) {
454: continue;
455: }
456:
457: Rectangle pathBounds = getBounds(path, null);
458: if (pathBounds == null) {
459: continue;
460: }
461:
462: int width = pathBounds.x + pathBounds.width;
463: if (result < width) {
464: result = width;
465: }
466:
467: if (path.equals(endPath)) {
468: break;
469: }
470: }
471:
472: return result;
473: }
474:
475: public int[] getRowsForPaths(final TreePath[] paths) {
476: if (paths == null) {
477: return new int[0];
478: }
479:
480: int result[] = new int[paths.length];
481: for (int i = 0; i < paths.length; i++) {
482: result[i] = getRowForPath(paths[i]);
483: }
484:
485: return result;
486: }
487:
488: protected Rectangle getNodeDimensions(final Object value,
489: final int row, final int depth, final boolean expanded,
490: final Rectangle placeIn) {
491:
492: return getNodeDimensions() != null ? getNodeDimensions()
493: .getNodeDimensions(value, row, depth, expanded, placeIn)
494: : null;
495: }
496:
497: protected boolean isFixedRowHeight() {
498: return getRowHeight() > 0;
499: }
500:
501: boolean isVisible(final TreePath path) {
502: if (!isModelPath(path)) {
503: return false;
504: }
505: if (isRoot(path)) {
506: return isRootVisible();
507: }
508:
509: if (!stateRoot.isExpanded()) {
510: return false;
511: }
512: StateNode nextNode = stateRoot;
513: for (int i = 1; i < path.getPathCount() - 1; i++) {
514: Object pathComponent = path.getPathComponent(i);
515: nextNode = nextNode.getChild(pathComponent);
516: if (nextNode == null || !nextNode.isExpanded()) {
517: return false;
518: }
519: }
520:
521: return nextNode.getModelIndexOfChild(path
522: .getLastPathComponent()) != -1;
523: }
524:
525: StateNode getStateRoot() {
526: return stateRoot;
527: }
528:
529: boolean isRoot(final TreePath path) {
530: return getStateRoot() != null && path != null
531: && path.equals(getStateRoot().getModelPath());
532: }
533:
534: StateNode getStateNodeForPath(final TreePath path) {
535: if (stateRoot == null || !isModelPath(path)) {
536: return null;
537: }
538:
539: StateNode result = stateRoot;
540: for (int i = 1; i < path.getPathCount(); i++) {
541: Object pathComponent = path.getPathComponent(i);
542: result = result.getChild(pathComponent);
543: if (result == null) {
544: return null;
545: }
546: }
547:
548: return result;
549: }
550:
551: int getVisibleChildCountImpl(final TreePath path) {
552: StateNode correspondingNode = getVisibleStateNodeForPath(path);
553: return correspondingNode != null ? correspondingNode
554: .getTotalChildrenCount() : 0;
555: }
556:
557: Enumeration<TreePath> getVisiblePathsFromImpl(final TreePath path) {
558: if (!isModelPath(path) || !isRoot(path) && !isVisible(path)) {
559: return null;
560: }
561:
562: return new Enumeration<TreePath>() {
563: private TreePath currentPath = path;
564:
565: public TreePath nextElement() {
566: if (currentPath == null) {
567: throw new NoSuchElementException();
568: }
569:
570: TreePath result = currentPath;
571:
572: TreePath childPath = getFirstChildPath(currentPath);
573: if (childPath != null) {
574: currentPath = childPath;
575: } else {
576: currentPath = getNextSiblingPathInHierarchy(currentPath);
577: }
578:
579: return result;
580: }
581:
582: public boolean hasMoreElements() {
583: return currentPath != null;
584: }
585:
586: private TreePath getFirstChildPath(final TreePath path) {
587: if (!isExpanded(path)) {
588: return null;
589: }
590:
591: StateNode node = getStateNodeForPath(path);
592: if (node.getModelChildCount() == 0) {
593: return null;
594: }
595:
596: Object child = node.getModelChildNode(0);
597: return path.pathByAddingChild(child);
598: }
599:
600: private TreePath getNextSiblingPathInHierarchy(
601: final TreePath path) {
602: TreePath currentPath = path;
603: while (true) {
604: TreePath siblingPath = getNextSiblingPath(currentPath);
605: if (siblingPath != null) {
606: return siblingPath;
607: }
608: currentPath = currentPath.getParentPath();
609: if (currentPath == null) {
610: return null;
611: }
612: }
613: }
614:
615: private TreePath getNextSiblingPath(final TreePath path) {
616: TreePath parentPath = path.getParentPath();
617: if (parentPath == null) {
618: return null;
619: }
620: StateNode parentNode = getStateNodeForPath(parentPath);
621: int curIndex = parentNode.getModelIndexOfChild(path
622: .getLastPathComponent());
623: int childCount = parentNode.getModelChildCount();
624: if (curIndex + 1 < childCount) {
625: Object sibling = parentNode
626: .getModelChildNode(curIndex + 1);
627: return parentPath.pathByAddingChild(sibling);
628: }
629: return null;
630: }
631: };
632: }
633:
634: void setExpandedStateImpl(final TreePath path,
635: final boolean isExpanded) {
636: if (isExpanded) {
637: expandPath(path);
638: } else {
639: collapsePath(path);
640: }
641: }
642:
643: boolean getExpandedStateImpl(final TreePath path) {
644: StateNode node = getVisibleStateNodeForPath(path);
645: return node != null && node.isExpanded();
646: }
647:
648: int getRowCountImpl() {
649: return getStateRoot() != null ? getStateRoot()
650: .getTotalChildrenCount()
651: + (isRootVisible() ? 1 : 0) : 0;
652: }
653:
654: Rectangle getFixedHeightBoundsImpl(final TreePath path,
655: final Rectangle placeIn) {
656: int row = getRowForPath(path);
657:
658: Rectangle result;
659: if (nodeDimensions != null) {
660: StateNode node = getStateNodeForPath(path);
661: result = getNodeDimensions(path.getLastPathComponent(),
662: row, getPathDepth(path), node != null
663: && node.isExpanded(), placeIn);
664: } else {
665: result = placeIn != null ? placeIn : new Rectangle();
666: }
667: result.y = row * rowHeight;
668: result.height = rowHeight;
669:
670: return result;
671: }
672:
673: TreePath getFixedHeightPathClosestToImpl(final int x, final int y) {
674: int rowCount = getRowCount();
675: if (rowCount == 0) {
676: return null;
677: }
678:
679: int row = Math.round(y / rowHeight);
680: if (row < 0) {
681: row = 0;
682: } else if (row >= rowCount) {
683: row = rowCount - 1;
684: }
685:
686: return getPathForRow(row);
687: }
688:
689: int getPathDepth(final TreePath path) {
690: return path.getPathCount() - 1;
691: }
692:
693: void treeNodesChangedImpl(final TreeModelEvent e) {
694: StateNode changeRootNode = getStateNodeForPath(e.getTreePath());
695: if (changeRootNode == null) {
696: changeRootNode = getStateNodeForPath(e.getTreePath()
697: .getParentPath());
698: }
699: if (changeRootNode == null) {
700: return;
701: }
702: changeRootNode.invalidate();
703: }
704:
705: void treeNodesInsertedImpl(final TreeModelEvent e) {
706: StateNode insertRootNode = getStateNodeForPath(e.getTreePath());
707: if (insertRootNode != null) {
708: insertRootNode.invalidateTreePartBelow();
709: resetRowSelection();
710: }
711: }
712:
713: void treeNodesRemovedImpl(final TreeModelEvent e) {
714: StateNode removeRootNode = getStateNodeForPath(e.getTreePath());
715: if (removeRootNode == null) {
716: removeRootNode = getStateNodeForPath(e.getTreePath()
717: .getParentPath());
718: if (removeRootNode == null) {
719: return;
720: }
721: removeRootNode.invalidateTreePartBelow();
722: resetRowSelection();
723: return;
724: }
725:
726: for (int i = 0; i < e.getChildren().length; i++) {
727: StateNode node = removeRootNode
728: .getChild(e.getChildren()[i]);
729: if (node != null) {
730: removeRootNode.remove(node);
731: }
732: }
733: removeRootNode.invalidateTreePartBelow();
734: resetRowSelection();
735: }
736:
737: void treeStructureChangedImpl(final TreeModelEvent e) {
738: if (stateRoot.getModelNode() != treeModel.getRoot()) {
739: resetRoot(treeModel);
740: }
741: TreePath path = e.getTreePath();
742: StateNode node = getStateNodeForPath(path);
743: if (node == null) {
744: return;
745: }
746: node.removeAll();
747: resetRowSelection();
748: }
749:
750: StateNode createStateNode(final StateNode parent,
751: final TreePath path) {
752: return new StateNode(parent, path);
753: }
754:
755: private void expandPath(final TreePath path) {
756: if (!isModelPath(path)) {
757: return;
758: }
759:
760: StateNode nextNode = stateRoot;
761: stateRoot.setExpanded();
762: for (int i = 1; i < path.getPathCount(); i++) {
763: Object pathComponent = path.getPathComponent(i);
764: if (!getModel().isLeaf(pathComponent)) {
765: nextNode = nextNode.addChild(pathComponent);
766: nextNode.setExpanded();
767: }
768: }
769: }
770:
771: private void collapsePath(final TreePath path) {
772: StateNode stateNode = getStateNodeForPath(path);
773: if (stateNode == null) {
774: return;
775: }
776:
777: if (stateNode.isLeaf() && !stateNode.isRoot()) {
778:
779: stateNode.getParent().remove(stateNode);
780: return;
781: }
782: stateNode.setCollapsed();
783: }
784:
785: private StateNode getVisibleStateNodeForPath(final TreePath path) {
786: if (stateRoot == null || !isModelPath(path)) {
787: return null;
788: }
789:
790: StateNode result = stateRoot;
791: for (int i = 1; i < path.getPathCount(); i++) {
792: if (!result.isExpanded()) {
793: return null;
794: }
795: Object pathComponent = path.getPathComponent(i);
796: result = result.getChild(pathComponent);
797: if (result == null) {
798: return null;
799: }
800: }
801:
802: return result;
803: }
804:
805: private boolean isModelPath(final TreePath path) {
806: return getStateRoot() != null && path != null
807: && getStateRoot().getModelPath().isDescendant(path);
808: }
809:
810: private void resetRowSelection() {
811: if (getSelectionModel() != null) {
812: getSelectionModel().resetRowSelection();
813: }
814: }
815:
816: private void resetRoot(final TreeModel model) {
817: if (model != null && model.getRoot() != null) {
818: stateRoot = createStateNode(null, new TreePath(model
819: .getRoot()));
820: if (model.isLeaf(model.getRoot())) {
821: stateRoot.setCollapsed();
822: }
823: } else {
824: stateRoot = null;
825: }
826: }
827: }
|