001: /*
002: The contents of this file are subject to the Common Public Attribution License
003: Version 1.0 (the "License"); you may not use this file except in compliance with
004: the License. You may obtain a copy of the License at
005: http://www.projity.com/license . The License is based on the Mozilla Public
006: License Version 1.1 but Sections 14 and 15 have been added to cover use of
007: software over a computer network and provide for limited attribution for the
008: Original Developer. In addition, Exhibit A has been modified to be consistent
009: with Exhibit B.
010:
011: Software distributed under the License is distributed on an "AS IS" basis,
012: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
013: specific language governing rights and limitations under the License. The
014: Original Code is OpenProj. The Original Developer is the Initial Developer and
015: is Projity, Inc. All portions of the code written by Projity are Copyright (c)
016: 2006, 2007. All Rights Reserved. Contributors Projity, Inc.
017:
018: Alternatively, the contents of this file may be used under the terms of the
019: Projity End-User License Agreeement (the Projity License), in which case the
020: provisions of the Projity License are applicable instead of those above. If you
021: wish to allow use of your version of this file only under the terms of the
022: Projity License and not to allow others to use your version of this file under
023: the CPAL, indicate your decision by deleting the provisions above and replace
024: them with the notice and other provisions required by the Projity License. If
025: you do not delete the provisions above, a recipient may use your version of this
026: file under either the CPAL or the Projity License.
027:
028: [NOTE: The text of this license may differ slightly from the text of the notices
029: in Exhibits A and B of the license at http://www.projity.com/license. You should
030: use the latest text at http://www.projity.com/license for your modifications.
031: You may not remove this license text from the source files.]
032:
033: Attribution Information: Attribution Copyright Notice: Copyright � 2006, 2007
034: Projity, Inc. Attribution Phrase (not exceeding 10 words): Powered by OpenProj,
035: an open source solution from Projity. Attribution URL: http://www.projity.com
036: Graphic Image as provided in the Covered Code as file: openproj_logo.png with
037: alternatives listed on http://www.projity.com/logo
038:
039: Display of Attribution Information is required in Larger Works which are defined
040: in the CPAL as a work which combines Covered Code or portions thereof with code
041: not governed by the terms of the CPAL. However, in addition to the other notice
042: obligations, all copies of the Covered Code in Executable and Source Code form
043: distributed must, as a form of attribution of the original author, include on
044: each user interface screen the "OpenProj" logo visible to all users. The
045: OpenProj logo should be located horizontally aligned with the menu bar and left
046: justified on the top left of the screen adjacent to the File menu. The logo
047: must be at least 100 x 25 pixels. When users click on the "OpenProj" logo it
048: must direct them back to http://www.projity.com.
049: */
050: package com.projity.grouping.core.hierarchy;
051:
052: import java.util.ArrayList;
053: import java.util.Collection;
054: import java.util.Comparator;
055: import java.util.Enumeration;
056: import java.util.HashMap;
057: import java.util.HashSet;
058: import java.util.Iterator;
059: import java.util.LinkedList;
060: import java.util.List;
061: import java.util.ListIterator;
062:
063: import javax.swing.event.EventListenerList;
064: import javax.swing.event.TreeModelListener;
065: import javax.swing.tree.TreePath;
066:
067: import com.projity.association.AssociationList;
068: import com.projity.configuration.Settings;
069: import com.projity.grouping.core.LazyParent;
070: import com.projity.grouping.core.Node;
071: import com.projity.grouping.core.NodeBridge;
072: import com.projity.grouping.core.NodeFactory;
073: import com.projity.grouping.core.model.NodeModel;
074: import com.projity.grouping.core.model.NodeModelUtil;
075: import com.projity.pm.assignment.Assignment;
076: import com.projity.pm.dependency.Dependency;
077: import com.projity.pm.dependency.DependencyService;
078: import com.projity.pm.resource.Resource;
079: import com.projity.pm.resource.ResourceImpl;
080: import com.projity.pm.resource.ResourcePool;
081: import com.projity.pm.snapshot.Snapshottable;
082: import com.projity.pm.task.Project;
083: import com.projity.pm.task.SubProj;
084: import com.projity.pm.task.Task;
085: import com.projity.pm.task.TaskSnapshot;
086: import com.projity.undo.NodeIndentEdit;
087: import com.projity.undo.NodeUndoInfo;
088: import com.projity.util.Alert;
089:
090: /**
091: * A map that holds the parent-children relationship. Also implements TreeModel so it can be used to generate
092: * trees, such as in outline cells or popup trees.
093: */
094: public class MutableNodeHierarchy extends AbstractMutableNodeHierarchy {
095: public static int DEFAULT_NB_END_VOID_NODES = 50;
096: public static int DEFAULT_NB_MULTIPROJECT_END_VOID_NODES = 1;
097:
098: private final Node root = NodeFactory.getInstance()
099: .createRootNode();
100:
101: protected int nbEndVoidNodes = DEFAULT_NB_END_VOID_NODES;
102: protected int nbMultiprojectEndVoidNodes = DEFAULT_NB_MULTIPROJECT_END_VOID_NODES;
103:
104: public MutableNodeHierarchy() {
105: }
106:
107: public static boolean isEvent(int actionType) {
108: return (actionType & NodeModel.EVENT) == NodeModel.EVENT;
109: }
110:
111: public static boolean isUndo(int actionType) {
112: return (actionType & NodeModel.UNDO) == NodeModel.UNDO;
113: }
114:
115: // private void setInSubproject(Node parent,Node node){
116: // if (NodeModelUtil.nodeIsSubproject(parent))
117: // node.setInSubproject(true);
118: // else
119: // node.setInSubproject(parent.isInSubproject());
120: // }
121:
122: private void setSubprojectLevel(Node node, int level) {
123: node.setSubprojectLevel(level);
124: int subprojectLevel = getChildrenSubprojectLevel(node);
125: for (Enumeration e = node.children(); e.hasMoreElements();) {
126: Node child = (Node) e.nextElement();
127: setSubprojectLevel(child, subprojectLevel);
128: }
129:
130: }
131:
132: private int getChildrenSubprojectLevel(Node parent) {
133: if (parent == null || parent.isRoot())
134: return 0;
135: if (NodeModelUtil.nodeIsSubproject(parent))
136: return parent.getSubprojectLevel() + 1;
137: else
138: return parent.getSubprojectLevel();
139: }
140:
141: public void add(Node parent, List children, int position,
142: int actionType) {
143: Node p = (parent == null) ? root : parent;
144: // ArrayList trees=new ArrayList();
145: // extractParents(children,trees);
146: if (/*trees*/children.size() == 0)
147: return;
148:
149: int subprojectLevel = getChildrenSubprojectLevel(parent);
150:
151: int childCount = p.getChildCount();
152: if (position > childCount) {
153: NodeFactory nodeFactory = NodeFactory.getInstance();
154: for (int i = childCount; i < position; i++) {
155: Node node = nodeFactory.createVoidNode();
156: setSubprojectLevel(node, subprojectLevel);
157: p.add(node);
158: }
159: }
160:
161: int j = position;
162: for (Iterator i = /*trees*/children.iterator(); i.hasNext();) {
163: Node node = (Node) i.next();
164: //if (node.getImpl() instanceof Task) System.out.println("ADD parent="+parent+":"+(parent==null?"X":parent.isInSubproject())+", node="+node+":"+node.isInSubproject());
165: setSubprojectLevel(node, subprojectLevel);
166: //if (node.getImpl() instanceof Task) System.out.println("ADD node in sub="+node.isInSubproject());
167: if (position == -1)
168: p.add(node);
169: else
170: p.insert(node, j++);
171: }
172: if (isEvent(actionType)) {
173: renumber();
174: fireNodesInserted(this , addDescendants(children/*trees*/));
175: }
176: }
177:
178: public void paste(Node parent, List children, int position,
179: NodeModel model, int actionType) {
180: Node p = (parent == null) ? root : parent;
181:
182: Project project = null;
183: ResourcePool resourcePool = null;
184: if (model.getDataFactory() instanceof Project)
185: project = (Project) model.getDataFactory();
186: else if (model.getDataFactory() instanceof ResourcePool)
187: resourcePool = (ResourcePool) model.getDataFactory();
188:
189: int subprojectLevel = getChildrenSubprojectLevel(parent);
190:
191: // ArrayList trees=new ArrayList();
192: // HierarchyUtils.extractParents(children,trees);
193:
194: int childCount = p.getChildCount();
195: if (position > childCount) {
196: NodeFactory nodeFactory = NodeFactory.getInstance();
197: Node node = nodeFactory.createVoidNode();
198: for (int i = childCount; i < position; i++) {
199: setSubprojectLevel(node, subprojectLevel);
200: p.add(node);
201: }
202: }
203:
204: int j = position;
205: for (Iterator i = /*trees*/children.iterator(); i.hasNext();) {
206: Node node = (Node) i.next();
207: if ((project != null && node.getImpl() instanceof Task)
208: || (resourcePool != null && node.getImpl() instanceof Resource)
209: || node.isVoid()) {
210: //if (node.getImpl() instanceof Task) System.out.println("PASTE parent="+parent+":"+(parent==null?"X":parent.isInSubproject())+", node="+node+":"+node.isInSubproject());
211: setSubprojectLevel(node, subprojectLevel);
212: //if (node.getImpl() instanceof Task) System.out.println("PASTE node in sub="+node.isInSubproject());
213: if (position == -1)
214: p.add(node);
215: else
216: p.insert(node, j++);
217: }
218: }
219: Node[] descendants = addDescendants(/*trees*/children);
220:
221: ArrayList<Dependency> dependencies = new ArrayList<Dependency>();
222:
223: boolean doTransaction = model.getDocument() != null
224: && descendants.length > 0;
225: int transactionId = 0;
226: if (doTransaction)
227: transactionId = model.getDocument()
228: .fireMultipleTransaction(0, true);
229:
230: ArrayList<Node> insertedNodes = new ArrayList<Node>(
231: descendants.length);
232:
233: if (project != null) {
234: HashMap<Long, Resource> resourceMap = new HashMap<Long, Resource>();
235: for (Resource r : (Collection<Resource>) project
236: .getResourcePool().getResourceList())
237: resourceMap.put(r.getUniqueId(), r);
238:
239: Project owningProject;
240: if (parent != null && parent.getImpl() instanceof Task) {
241: Task task = (Task) parent.getImpl();
242: if (task.isSubproject())
243: owningProject = ((SubProj) task).getSubproject();
244: else
245: owningProject = task.getOwningProject();
246: } else
247: owningProject = (Project) model.getDataFactory();
248:
249: for (int i = 0; i < descendants.length; i++) {
250: if (descendants[i].getImpl() instanceof Task) {
251: Task task = (Task) descendants[i].getImpl();
252:
253: Node parentSubproject = getParentSubproject((Node) descendants[i]
254: .getParent());
255: if (parentSubproject != null)
256: owningProject = ((SubProj) parentSubproject
257: .getImpl()).getSubproject();
258: task.setProjectId(owningProject.getUniqueId()); //useful?
259: owningProject.validateObject(task, model, this ,
260: null, false);
261: List deps = task.getDependencyList(true);
262: if (deps != null)
263: dependencies.addAll(deps);
264:
265: //check assignments, if resource not present change it to unassigned
266:
267: for (int s = 0; s < Settings.numBaselines(); s++) {
268: TaskSnapshot snapshot = (TaskSnapshot) task
269: .getSnapshot(new Integer(s));
270: if (snapshot == null)
271: continue;
272: AssociationList snapshotAssignments = snapshot
273: .getHasAssignments().getAssignments();
274: if (snapshotAssignments.size() > 0) {
275: // ArrayList<Assignment> assignmentsToLink=new ArrayList<Assignment>();
276: for (Iterator a = snapshotAssignments
277: .listIterator(); a.hasNext();) {
278: Assignment assignment = (Assignment) a
279: .next();
280: Resource resource = assignment
281: .getResource();
282: if (resource == ResourceImpl
283: .getUnassignedInstance())
284: continue;
285: Resource destResource = resourceMap
286: .get(resource.getUniqueId());
287: if (destResource != null) {
288: if (Snapshottable.CURRENT.equals(s)) {
289: if (destResource != resource) { // use destination resource
290: resource = destResource;
291: assignment.getDetail()
292: .setResource(
293: resource);
294: }
295: // assignmentsToLink.add(assignment);
296: resource
297: .addAssignment(assignment);
298: NodeUndoInfo undo = new NodeUndoInfo(
299: false);
300: ((ResourcePool) assignment
301: .getResource()
302: .getDocument())
303: .getObjectEventManager()
304: .fireCreateEvent(this ,
305: assignment,
306: undo);
307: }
308: } else {
309: assignment
310: .getDetail()
311: .setResource(
312: ResourceImpl
313: .getUnassignedInstance());
314: }
315: }
316: // for (Assignment assignment: assignmentsToLink){
317: // AssignmentService.getInstance().remove(assignmentsToLink, this,false);
318: // AssignmentService.getInstance().connect(assignment, this);
319: // }
320:
321: }
322: }
323:
324: project.initializeId(task);
325: project.addPastedTask(task);
326:
327: insertedNodes.add(descendants[i]);
328: }
329: }
330: } else if (resourcePool != null) {
331: for (int i = 0; i < descendants.length; i++) {
332: if (descendants[i].getImpl() instanceof Resource) {
333: Resource resource = (Resource) descendants[i]
334: .getImpl();
335: model.getDataFactory().validateObject(resource,
336: model, this , null, false);
337: resourcePool.initializeId(resource);
338: insertedNodes.add(descendants[i]);
339: }
340: }
341: }
342: if (doTransaction)
343: model.getDocument().fireMultipleTransaction(transactionId,
344: false);
345:
346: if (isEvent(actionType)) {
347: renumber();
348: fireNodesInserted(this , insertedNodes
349: .toArray(new Node[insertedNodes.size()]));
350:
351: //not necessary in case of subproject paste
352: for (Dependency dependency : dependencies) {
353: DependencyService.getInstance().updateSentinels(
354: dependency); //needed?
355: dependency.fireCreateEvent(this );
356: }
357: }
358: }
359:
360: private Node getParentSubproject(Node node) {
361: if (node.isRoot())
362: return null;
363: else if (node.getImpl() instanceof SubProj)
364: return node;
365: else
366: return getParentSubproject((Node) node.getParent());
367: }
368:
369: // public void add(Node parent,Node child,int actionType){
370: // add(parent,child,-1,actionType);
371: // }
372: // public void add(Node parent,List children,int actionType){
373: // add(parent,children,-1,actionType);
374: // }
375: //
376: // public void add(Node parent,Node child,int position,int actionType){
377: // LinkedList children=new LinkedList();
378: // children.add(child);
379: // add(parent,children,position,actionType);
380: // }
381:
382: public void cleanVoidChildren() {
383: cleanVoidChildren(root);
384: }
385:
386: private void cleanVoidChildren(Node node) {
387: for (Iterator i = node.childrenIterator(); i.hasNext();) {
388: Node child = (Node) i.next();
389: if (child.isVoid())
390: i.remove();
391: else
392: cleanVoidChildren(child);
393: }
394: }
395:
396: //utility
397: public static void addDescendants(Node node, List descendants) {
398: for (Enumeration e = ((NodeBridge) node).preorderEnumeration(); e
399: .hasMoreElements();)
400: descendants.add(e.nextElement());
401: }
402:
403: //nodes are roots of trees
404: public static void addDescendants(List nodes, List descendants) {
405: for (Iterator i = nodes.iterator(); i.hasNext();)
406: addDescendants((Node) i.next(), descendants);
407: }
408:
409: //warning: modify nodes list
410: private static Node[] addDescendants(List nodes) {
411: ArrayList descendants = new ArrayList();
412: for (ListIterator i = nodes.listIterator(); i.hasNext();) {
413: Node node = (Node) i.next();
414: extractSameProjectBranch(node, descendants);
415: // boolean rootNode=true;
416: // for (Enumeration e=((NodeBridge)node).preorderEnumeration();e.hasMoreElements();rootNode=false){
417: // Node current=(Node)e.nextElement();
418: // if (!rootNode) descendants.add(current);
419: // }
420: }
421: Node[] descendantsArray = (Node[]) descendants
422: .toArray(new Node[descendants.size()]);
423: return descendantsArray;
424: }
425:
426: private static void extractSameProjectBranch(Node parent,
427: ArrayList descendants) {
428: // if (parent.getImpl() instanceof Subproject){
429: // ((NodeBridge)parent).removeAllChildren();
430: // Subproject subproject=(Subproject)parent.getImpl();
431: // //subproject.setProject(null);
432: //
433: // }else{
434: descendants.add(parent);
435: for (Enumeration e = parent.children(); e.hasMoreElements();) {
436: Node current = (Node) e.nextElement();
437: extractSameProjectBranch(current, descendants);
438: }
439: // }
440: }
441:
442: /**
443: * Remove nodes. This will wrap the call in a multiple transaction if there are many calls so as
444: * not to recalculate each time. In case end void nodes were removed, they will be put back
445: *
446: */
447: public void remove(List nodes, NodeModel model, int actionType,
448: boolean removeDependencies) {
449: if (nodes != null) {
450:
451: boolean doTransaction = model.getDocument() != null
452: && nodes.size() > 0 && isEvent(actionType);
453: int transactionId = 0;
454: if (doTransaction)
455: transactionId = model.getDocument()
456: .fireMultipleTransaction(0, true);
457: ArrayList removed = new ArrayList();
458: for (Iterator i = nodes.iterator(); i.hasNext();) {
459: LinkedList toRemove = new LinkedList();
460: removeSubTree((Node) i.next(), model, toRemove,
461: actionType, removeDependencies);
462: removed.addAll(toRemove);
463: }
464: if (isEvent(actionType)) {
465: renumber();
466: fireNodesRemoved(this , removed.toArray());
467: }
468: if (doTransaction)
469: model.getDocument().fireMultipleTransaction(
470: transactionId, false);
471: }
472: }
473:
474: // public void remove(Node node,NodeModel model,int actionType){
475: // LinkedList removed=new LinkedList();
476: // removeNoEvent(node,model,removed,actionType);
477: // fireNodesRemoved(this,removed.toArray());
478: // }
479:
480: // private void removeNoEvent(Node node,NodeModel model,LinkedList toRemove,int actionType){
481: //
482: // System.out.println("removeNoEvent("+node+")");
483: // //if (!isEvent(actionType)) return;
484: // //node.removeFromParent();
485: // Node current;
486: // int badCount = 0;
487: // LinkedList enumeratedNodes=new LinkedList();
488: // for (Enumeration e=((NodeBridge)node).postorderEnumeration();e.hasMoreElements();){
489: // enumeratedNodes.add(e.nextElement());
490: // }
491: // System.out.println("removeApartFromHierarchy("+enumeratedNodes+")");
492: // for (Iterator i=enumeratedNodes.iterator();i.hasNext();){
493: // current=(Node)i.next();
494: // if (model.removeApartFromHierarchy(current,actionType))
495: // toRemove.add(current);
496: // else
497: // badCount++;
498: // }
499: //// for (Enumeration e=((NodeBridge)node).postorderEnumeration();e.hasMoreElements();){
500: //// current=(Node)e.nextElement();
501: //// System.out.println("removeApartFromHierarchy("+current+")");
502: //// if (model.removeApartFromHierarchy(current,actionType))
503: //// toRemove.add(current);
504: //// else
505: //// badCount++;
506: //// }
507: //// if (badCount == 0) // if no errors, the
508: // node.removeFromParent();
509: //
510: //
511: //
512: //// if (undo){
513: //// //Undo
514: //// NodeHierarchyVoidLocation location=new NodeHierarchyVoidLocation(new NodeHierarchyLocation(parent,previous),1);
515: //// UndoableEditSupport undoableEditSupport=model.getUndoableEditSupport();
516: //// if (undoableEditSupport!=null){
517: //// undoableEditSupport.postEdit(new NodeDeletionEdit(model,location,node));
518: //// }
519: //// }
520: //
521: //
522: // //fireNodesRemoved(this,toRemove.toArray());
523: // }
524:
525: private void removeSubTree(Node node, NodeModel model,
526: LinkedList toRemove, int actionType,
527: boolean removeDependencies) {
528: // System.out.println("removeSubTree");
529: if (getUpdateLevel() == 0) {
530: //boolean singleRemoval=!(node.getImpl() instanceof Assignment);
531: try {
532: node.removeFromParent();
533: /*if (singleRemoval)*/beginUpdate();
534: // System.out.println("removeNoEvent("+node+")");
535: Node current;
536: int badCount = 0;
537: LinkedList enumeratedNodes = new LinkedList();
538: for (Enumeration e = ((NodeBridge) node)
539: .postorderEnumeration(); e.hasMoreElements();) {
540: enumeratedNodes.add(e.nextElement());
541: }
542: // System.out.println("removeApartFromHierarchy("+enumeratedNodes+")");
543: for (Iterator i = enumeratedNodes.iterator(); i
544: .hasNext();) {
545: current = (Node) i.next();
546: if (model.removeApartFromHierarchy(current, false,
547: actionType, removeDependencies))
548: toRemove.add(current);
549: else
550: badCount++;
551: }
552: node.removeFromParent();
553: } finally {
554: /*if (singleRemoval)*/endUpdate();
555: }
556: }
557: }
558:
559: public void removeAll(NodeModel model, int actionType) {
560: remove(buildList(), model, actionType, true);
561: }
562:
563: public void move(Node node, Node newParent, int actionType) {
564: setSubprojectLevel(node, getChildrenSubprojectLevel(newParent));
565: newParent.add(node);
566: ArrayList change = new ArrayList();
567: for (Enumeration e = ((NodeBridge) node).preorderEnumeration(); e
568: .hasMoreElements();)
569: change.add(e.nextElement());
570:
571: if (isEvent(actionType))
572: fireNodesChanged(this , change.toArray());
573: }
574:
575: //indentation
576: // public void indent(Node node,int deltaLevel,int actionType){
577: // internalIndent(node,deltaLevel,actionType);
578: // }
579: public void indent(List nodes, int deltaLevel, NodeModel model,
580: int actionType) {
581: boolean doTransaction = model.getDocument() != null;
582: int transactionId = 0;
583: if (doTransaction)
584: transactionId = model.getDocument()
585: .fireMultipleTransaction(0, true);
586: List changedParents = internalIndent(nodes, deltaLevel,
587: actionType);
588: if (doTransaction)
589: model.getDocument().fireMultipleTransaction(transactionId,
590: false);
591: if (model.getUndoableEditSupport() != null & isUndo(actionType)
592: && changedParents != null && changedParents.size() > 0) {
593: model.getUndoableEditSupport().postEdit(
594: new NodeIndentEdit(model, changedParents,
595: deltaLevel));
596: }
597: }
598:
599: //nodes have to be ordered from first to last
600: private List internalIndent(List nodes, int deltaLevel,
601: int actionType) {
602: if (deltaLevel != 1 && deltaLevel != -1)
603: return null;
604:
605: //Indent only parents
606: LinkedList nodesToChange = new LinkedList();
607: HierarchyUtils.extractParents(nodes, nodesToChange);
608:
609: List modifiedVoids = new ArrayList();
610:
611: //exclude Assignments and VoidNodes
612: if (deltaLevel > 0) {
613: for (ListIterator i = nodesToChange.listIterator(); i
614: .hasNext();) {
615: if (!internalIndent((Node) i.next(), deltaLevel,
616: actionType & NodeModel.UNDO, modifiedVoids))
617: i.remove();
618: for (Iterator j = modifiedVoids.iterator(); j.hasNext();) {
619: i.add(j.next());
620: }
621: modifiedVoids.clear();
622: }
623: } else {
624: for (ListIterator i = nodesToChange
625: .listIterator(nodesToChange.size()); i
626: .hasPrevious();) {
627: if (!internalIndent((Node) i.previous(), deltaLevel,
628: actionType & NodeModel.UNDO, modifiedVoids))
629: i.remove();
630: for (Iterator j = modifiedVoids.iterator(); j.hasNext();) {
631: i.add(j.next());
632: }
633: modifiedVoids.clear();
634: }
635: }
636:
637: if (isEvent(actionType) && nodesToChange.size() > 0)
638: fireNodesChanged(this , nodesToChange.toArray());
639: return nodesToChange;
640: }
641:
642: private boolean internalIndent(Node node, int deltaLevel,
643: int actionType, List modifiedVoids) { //only +1 -1
644: if (node == null || node == root
645: || !node.isIndentable(deltaLevel))
646: return false;
647: if (deltaLevel == 1) { //indent
648: Node parent = getParent(node);
649: int index = parent.getIndex(node);
650: if (index == 0)
651: return false;
652: Node sibling;
653: Node previous = null;
654:
655: for (ListIterator i = parent.childrenIterator(index); i
656: .hasPrevious();) {
657: sibling = (Node) i.previous();
658: if (node.canBeChildOf(sibling)) {
659: previous = sibling;
660: break;
661: } else if (sibling.isVoid()) {
662: modifiedVoids.add(sibling);
663: }
664: }
665: if (previous == null
666: || previous.getImpl() instanceof Assignment) {
667:
668: return false;
669: }
670: for (Iterator i = modifiedVoids.iterator(); i.hasNext();) {
671: previous.add((Node) i.next());
672: }
673: previous.add(node);
674: if (isEvent(actionType))
675: fireNodesChanged(this , new Node[] { node });
676: } else if (deltaLevel == -1) { //outdent
677: Node parent = getParent(node);
678: if (parent == null || parent == root)
679: return false;
680: if (parent.isLazyParent()) // don't allow outdenting of subprojects' children
681: return false;
682: Node grandParent = getParent(parent);
683:
684: //voids
685: int index = parent.getIndex(node);
686: if (index > 0) {
687: Node sibling;
688: for (ListIterator i = parent.childrenIterator(index); i
689: .hasPrevious();) {
690: sibling = (Node) i.previous();
691: if (sibling.isVoid()) {
692: modifiedVoids.add(sibling);
693: } else
694: break;
695: }
696: }
697:
698: index = grandParent.getIndex(parent) + 1;
699: grandParent.insert(node, index);
700: for (Iterator i = modifiedVoids.iterator(); i.hasNext();) {
701: grandParent.insert((Node) i.next(), index);
702: }
703: if (isEvent(actionType))
704: fireNodesChanged(this , new Node[] { node });
705: }
706: return true;
707: }
708:
709: public void fireUpdate() {
710: fireStructureChanged(this );
711: }
712:
713: public void fireUpdate(Node[] nodes) {
714: fireNodesChanged(this , nodes);
715: }
716:
717: public void fireInsertion(Node[] nodes) {
718: fireNodesInserted(this , nodes);
719: }
720:
721: public void fireRemoval(Node[] nodes) {
722: fireNodesRemoved(this , nodes);
723: }
724:
725: public Node getParent(Node child) {
726: if (child == null)
727: return null;
728: return (Node) child.getParent();
729: }
730:
731: public List getChildren(Node parent) {
732: return (parent == null) ? root.getChildren() : parent
733: .getChildren();
734: }
735:
736: public int getLevel(Node node) {
737: int level = 0;
738: for (Node current = node; current != null; current = getParent(current))
739: level++;
740: return level;
741: }
742:
743: private List buildList() {
744: return buildList(null);
745: }
746:
747: private List buildList(Node parent) {
748: List list = new ArrayList();
749: buildList(parent, list);
750: return list;
751: }
752:
753: private void buildList(Node parent, List list) {
754: Node p = (parent == null) ? root : parent;
755: if (p != root)
756: list.add(p);
757: Collection children = getChildren(p);
758: if (children != null) {
759: for (Iterator i = children.iterator(); i.hasNext();) {
760: buildList((Node) i.next(), list);
761: }
762: }
763: }
764:
765: //
766: // private Iterator iterator(){
767: // return buildList(null).iterator();
768: // }
769:
770: private Node searchPrevious(Node node) {
771: if (node == null || node == root)
772: return null;
773: Node previous = getParent(node);
774: Collection children = getChildren(previous);
775: if (children == null)
776: return null;
777: for (Iterator i = children.iterator(); i.hasNext();) {
778: Node currentNode = (Node) i.next();
779: if (currentNode.equals(node))
780: return previous;
781: previous = currentNode;
782: }
783: return null;
784: }
785:
786: private Node searchLast(Node node) {
787: Node current = (node == null) ? root : node;
788: while (!isLeaf(current)) {
789: List children = (List) getChildren(current);
790: current = (Node) children.get(children.size() - 1);
791: }
792: return current;
793: }
794:
795: private Node searchLast(int level) {
796: Node current = null;
797: for (int l = 0; !isLeaf(current) && l < level; l++) {
798: List children = (List) getChildren(current);
799: current = (Node) children.get(children.size() - 1);
800: }
801: return current;
802: }
803:
804: private boolean contains(Object node) {
805: //return parents.containsKey(node)||children.containsKey(node);
806: Alert.error("contains not implemented");
807: return false;
808: }
809:
810: public Object clone() {
811: Alert.error("clone not implemented");
812: return null;
813: //return new MutableNodeHierarchy((HashMap)parents.clone(),(MultiHashMap)children.clone(),(HashMap)voidNodes.clone());
814: }
815:
816: public Node search(Object key, Comparator c) {
817: // System.out.println("search("+key+", "+c+")");
818: return search(root, key, c);
819: }
820:
821: private Node search(Node node, Object key, Comparator c) {
822: if (c.compare((node == null) ? root : node, key) == 0)
823: return node;
824: Collection children = getChildren(node);
825: if (children == null)
826: return null;
827: Iterator i = children.iterator();
828: while (i.hasNext()) {
829: Node found = search((Node) i.next(), key, c);
830: if (found != null)
831: return found;
832: }
833: return null;
834: }
835:
836: public boolean isSummary(Node node) {
837: List children = getChildren(node);
838: if (children == null)
839: return false;
840: for (Iterator i = children.iterator(); i.hasNext();) {
841: if (!(((Node) i.next()).getImpl() instanceof Assignment))
842: return true;
843: }
844: return false;
845: }
846:
847: // Below is TreeModel implementation
848: public Object getRoot() {
849: return root;
850: }
851:
852: public boolean isLeaf(Object node) {
853: Collection children = getChildren((Node) node);
854: return (children == null || children.size() == 0);
855: }
856:
857: protected transient EventListenerList listenerList = new EventListenerList();
858:
859: /**
860: * Adds a listener for the TreeModelEvent posted after the tree changes.
861: *
862: * @see #removeTreeModelListener
863: * @param l the listener to add
864: */
865: public void addTreeModelListener(TreeModelListener l) {
866: listenerList.add(TreeModelListener.class, l);
867: }
868:
869: /**
870: * Removes a listener previously added with <B>addTreeModelListener()</B>.
871: *
872: * @see #addTreeModelListener
873: * @param l the listener to remove
874: */
875: public void removeTreeModelListener(TreeModelListener l) {
876: listenerList.remove(TreeModelListener.class, l);
877: }
878:
879: /* (non-Javadoc)
880: * @see javax.swing.tree.TreeModel#valueForPathChanged(javax.swing.tree.TreePath, java.lang.Object)
881: */
882: public void valueForPathChanged(TreePath path, Object newValue) {
883: Node aNode = (Node) path.getLastPathComponent();
884: //TODO do we need to treat this?
885: }
886:
887: protected boolean checkSubprojectEndVoidNodes(Node parent,
888: List inserted) {
889: Node node;
890: int count = 0;
891: boolean found = false;
892: for (Enumeration e = parent.children(); e.hasMoreElements();) {
893: node = (Node) e.nextElement();
894: if (checkSubprojectEndVoidNodes(node, inserted))
895: found = true;
896: if (NodeModelUtil.nodeIsSubproject(parent)) {
897: if (node.isVoid())
898: count++;
899: else
900: count = 0;
901: }
902: }
903: if (NodeModelUtil.nodeIsSubproject(parent)
904: && count < nbMultiprojectEndVoidNodes) {
905: LazyParent sub = (LazyParent) parent.getImpl();
906: if (sub.isDataFetched()) {
907: int subprojectLevel = getChildrenSubprojectLevel(parent);
908: for (int i = 0; i < nbMultiprojectEndVoidNodes - count; i++) {
909: node = NodeFactory.getInstance().createVoidNode();
910: setSubprojectLevel(node, subprojectLevel);
911: //node.setInSubproject(true);
912: parent.add(node);
913: inserted.add(node);
914: }
915: }
916: }
917: return found;
918: }
919:
920: public void checkEndVoidNodes(int actionType) {
921: checkEndVoidNodes(false, actionType);
922: }
923:
924: /**
925: * Check if void nodes have to be added to respect nbEndVoidNodes
926: * @param event
927: */
928: public void checkEndVoidNodes(boolean subproject, int actionType) {
929: ArrayList inserted = new ArrayList();
930:
931: if (!subproject)
932: checkSubprojectEndVoidNodes(root, inserted);
933: int nbEndVoids = subproject ? nbMultiprojectEndVoidNodes
934: : nbEndVoidNodes;
935: //int nbEndVoids=nbEndVoidNodes;
936: int count = 0;
937: Node node;
938: for (ListIterator i = root.childrenIterator(root
939: .getChildCount()); i.hasPrevious();) {
940: node = (Node) i.previous();
941: if (node.isVoid())
942: count++;
943: else
944: break;
945: }
946: if (count < nbEndVoids) {
947:
948: for (int i = 0; i < nbEndVoids - count; i++) {
949: node = NodeFactory.getInstance().createVoidNode();
950: root.add(node);
951: inserted.add(node);
952: }
953: } else if (count > nbEndVoids) { // remove void nodes if they shouldt be there
954: int removeCount = count - nbEndVoids;
955: for (ListIterator i = root.childrenIterator(root
956: .getChildCount()); i.hasPrevious();) {
957: node = (Node) i.previous();
958: if (node.isVoid()) {
959: i.remove();
960: removeCount--;
961: if (removeCount == 0)
962: break;
963: } else {
964: break;
965: }
966: }
967:
968: }
969: if (isEvent(actionType) && inserted.size() > 0)
970: fireNodesInserted(this , inserted.toArray()/*null*/);
971: }
972:
973: /**
974: * @return Returns the nbEndVoidNodes.
975: */
976: public int getNbEndVoidNodes() {
977: return nbEndVoidNodes;
978: }
979:
980: /**
981: * @param nbEndVoidNodes The nbEndVoidNodes to set. If -1, then use default
982: */
983: public void setNbEndVoidNodes(int nbEndVoidNodes) {
984: if (nbEndVoidNodes == -1)
985: nbEndVoidNodes = DEFAULT_NB_END_VOID_NODES;
986: this.nbEndVoidNodes = nbEndVoidNodes;
987: }
988:
989: }
|