001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.search;
043:
044: import java.awt.EventQueue;
045: import java.util.ArrayList;
046: import java.util.List;
047: import javax.swing.event.TreeModelEvent;
048: import javax.swing.event.TreeModelListener;
049: import javax.swing.tree.TreeModel;
050: import javax.swing.tree.TreePath;
051: import org.openide.nodes.Node;
052:
053: /**
054: *
055: *
056: * @author Marian Petras
057: */
058: final class ResultTreeModel implements TreeModel {
059:
060: /** */
061: final ResultModel resultModel;
062: /** */
063: private final TreePath rootPath;
064: /** */
065: private String rootDisplayName;
066: /** */
067: private boolean selected = true;
068: /** */
069: private int objectsCount;
070: /** */
071: private List<TreeModelListener> treeModelListeners;
072:
073: /**
074: *
075: * @param resultModel result model, or {@code null} for empty tree model
076: */
077: ResultTreeModel(ResultModel resultModel) {
078: this .resultModel = resultModel;
079: this .rootPath = new TreePath(this );
080:
081: if (resultModel != null) {
082: resultModel.setObserver(this );
083: }
084: }
085:
086: public Object getRoot() {
087: return this ;
088: }
089:
090: public Object getChild(Object parent, int index) {
091: assert EventQueue.isDispatchThread();
092:
093: if ((resultModel == null) || (index < 0)) {
094: return null;
095: }
096:
097: Object ret;
098: if (parent == getRoot()) {
099: if (index >= objectsCount) {
100: ret = null;
101: } else {
102: try {
103: //PENDING - threading:
104: ret = resultModel.matchingObjects.get(index);
105: } catch (ArrayIndexOutOfBoundsException ex) {
106: assert false;
107: ret = null;
108: }
109: }
110: } else if (parent.getClass() == MatchingObject.class) {
111: if (resultModel.canHaveDetails() == Boolean.FALSE) {
112: ret = null;
113: } else {
114: MatchingObject matchingObject = (MatchingObject) parent;
115: Node[] detailNodes = resultModel.searchAndReplace ? resultModel.basicCriteria
116: .getDetails(matchingObject.object)
117: : resultModel.getDetails(matchingObject);
118: if ((detailNodes == null)
119: || (index >= detailNodes.length)) {
120: ret = null;
121: } else {
122: ret = detailNodes[index];
123: }
124: }
125: } else { //detail node
126: ret = null;
127: }
128: return ret;
129: }
130:
131: public int getChildCount(Object parent) {
132: assert EventQueue.isDispatchThread();
133:
134: if (resultModel == null) {
135: return 0;
136: }
137:
138: int ret;
139: if (parent == getRoot()) {
140: ret = objectsCount;
141: } else if (parent.getClass() == MatchingObject.class) {
142: if (resultModel.searchAndReplace) {
143: ret = resultModel.basicCriteria
144: .getDetailsCount(((MatchingObject) parent).object);
145: } else if (resultModel.canHaveDetails() == Boolean.FALSE) {
146: ret = 0;
147: } else {
148: ret = resultModel
149: .getDetailsCount((MatchingObject) parent);
150: }
151: } else { //detail node
152: ret = 0;
153: }
154: return ret;
155: }
156:
157: public boolean isLeaf(Object node) {
158: assert EventQueue.isDispatchThread();
159:
160: boolean ret;
161: if (node == getRoot()) {
162: ret = false;
163: } else if (node.getClass() == MatchingObject.class) {
164: Boolean hasDetails = resultModel.canHaveDetails();
165: if (hasDetails != null) {
166: ret = !hasDetails.booleanValue();
167: } else {
168: ret = !resultModel.hasDetails((MatchingObject) node);
169: }
170: } else { //detail node
171: ret = true;
172: }
173: return ret;
174: }
175:
176: public void valueForPathChanged(TreePath path, Object newValue) {
177: assert EventQueue.isDispatchThread();
178:
179: /* This should never be called. None of the nodes is editable. */
180: assert false;
181: }
182:
183: public int getIndexOfChild(Object parent, Object child) {
184: assert EventQueue.isDispatchThread();
185:
186: if ((resultModel == null) || (parent == null)
187: || (child == null)) {
188: return -1;
189: }
190:
191: int ret;
192: if (parent == getRoot()) {
193: ret = (child.getClass() == MatchingObject.class) ? resultModel.matchingObjects
194: .indexOf(child)
195: : -1;
196: } else {
197: ret = -1;
198: if ((parent.getClass() == MatchingObject.class)
199: && resultModel.canHaveDetails()
200: && (child instanceof Node)) {
201: MatchingObject matchingObject = (MatchingObject) parent;
202: Node[] detailNodes = resultModel.searchAndReplace ? resultModel.basicCriteria
203: .getDetails(matchingObject.object)
204: : resultModel.getDetails(matchingObject);
205: if (detailNodes != null) {
206: for (int i = 0; i < detailNodes.length; i++) {
207: if (detailNodes[i].equals(child)) {
208: ret = i;
209: break;
210: }
211: }
212: }
213: }
214: }
215: return ret;
216: }
217:
218: public void addTreeModelListener(TreeModelListener l) {
219: assert EventQueue.isDispatchThread();
220:
221: if (l == null) {
222: throw new IllegalArgumentException("null"); //NOI18N
223: }
224:
225: if (treeModelListeners == null) {
226: treeModelListeners = new ArrayList<TreeModelListener>(4);
227: }
228: treeModelListeners.add(l);
229: }
230:
231: public void removeTreeModelListener(TreeModelListener l) {
232: assert EventQueue.isDispatchThread();
233:
234: if (l == null) {
235: throw new IllegalArgumentException("null"); //NOI18N
236: }
237:
238: if (treeModelListeners != null) {
239: treeModelListeners.remove(l);
240: }
241: }
242:
243: /**
244: */
245: void objectFound(MatchingObject object, int objectIndex) {
246: if (resultModel == null) {
247: throw new IllegalStateException("resultModel is null"); //NOI18N
248: }
249: new Task(object, objectIndex).run();//fireNodeAdded(objectIndex, object);
250: }
251:
252: /**
253: */
254: void objectBecameInvalid(MatchingObject object) {
255: if (resultModel == null) {
256: throw new IllegalStateException("resultModel is null"); //NOI18N
257: }
258: new Task(object).run();
259: }
260:
261: /**
262: */
263: String getRootDisplayName() {
264: assert EventQueue.isDispatchThread();
265:
266: return rootDisplayName;
267: }
268:
269: /** */
270: void setRootDisplayName(String displayName) {
271: assert EventQueue.isDispatchThread();
272:
273: this .rootDisplayName = displayName;
274: UPDATE_NAME_TASK.run(); //fireRootNodeChanged();
275: }
276:
277: /**
278: */
279: boolean isSelected() {
280: return selected;
281: }
282:
283: /**
284: */
285: void setSelected(boolean selected) {
286: if (selected == this .selected) {
287: return;
288: }
289:
290: this .selected = selected;
291: }
292:
293: private final Task UPDATE_NAME_TASK = new Task();
294:
295: /**
296: * Single class for sending various asynchronous tasks to the event queue.
297: */
298: private final class Task implements Runnable {
299: private final MatchingObject foundObject;
300: private final int foundObjectIndex;
301:
302: private Task() {
303: this .foundObject = null;
304: this .foundObjectIndex = -1;
305: }
306:
307: private Task(MatchingObject object) {
308: this .foundObject = object;
309: this .foundObjectIndex = -1;
310: }
311:
312: private Task(MatchingObject foundObject, int foundObjectIndex) {
313: assert (foundObject != null) && (foundObjectIndex >= 0);
314: this .foundObject = foundObject;
315: this .foundObjectIndex = foundObjectIndex;
316: }
317:
318: public void run() {
319: if (!EventQueue.isDispatchThread()) {
320: EventQueue.invokeLater(this );
321: return;
322: }
323:
324: assert EventQueue.isDispatchThread();
325: if (foundObject != null) {
326: if (foundObjectIndex != -1) {
327: objectsCount++;
328: fireNodeAdded(foundObjectIndex, foundObject);
329: } else {
330: fireNodeChanged(foundObject);
331: }
332: } else {
333: fireRootNodeChanged();
334: }
335: }
336: }
337:
338: /**
339: */
340: private void fireNodeAdded(int index, MatchingObject object) {
341: assert EventQueue.isDispatchThread();
342:
343: if ((treeModelListeners == null)
344: || treeModelListeners.isEmpty()) {
345: return;
346: }
347:
348: TreeModelEvent event = new TreeModelEvent(this , rootPath,
349: new int[] { index }, new Object[] { object });
350: for (TreeModelListener l : treeModelListeners) {
351: l.treeNodesInserted(event);
352: }
353: }
354:
355: /**
356: */
357: private void fireNodeChanged(MatchingObject object) {
358: assert EventQueue.isDispatchThread();
359:
360: if ((treeModelListeners == null)
361: || treeModelListeners.isEmpty()) {
362: return;
363: }
364:
365: TreePath path = rootPath.pathByAddingChild(object);
366: TreeModelEvent event = new TreeModelEvent(this , path);
367: for (TreeModelListener l : treeModelListeners) {
368: l.treeStructureChanged(event);
369: }
370: }
371:
372: /**
373: */
374: void fireRootNodeChanged() {
375: assert EventQueue.isDispatchThread();
376:
377: if ((treeModelListeners == null)
378: || treeModelListeners.isEmpty()) {
379: return;
380: }
381:
382: TreeModelEvent event = new TreeModelEvent(this , rootPath, null,
383: null);
384: for (TreeModelListener l : treeModelListeners) {
385: l.treeNodesChanged(event);
386: }
387: }
388:
389: /**
390: */
391: void fireFileNodesSelectionChanged(int[] indices,
392: MatchingObject[] matchingObjects) {
393: assert EventQueue.isDispatchThread();
394: assert matchingObjects != null;
395: assert indices != null;
396: assert matchingObjects.length == indices.length;
397:
398: if ((treeModelListeners == null)
399: || treeModelListeners.isEmpty()) {
400: return;
401: }
402:
403: TreeModelEvent event = new TreeModelEvent(this , rootPath,
404: indices, matchingObjects);
405: for (TreeModelListener l : treeModelListeners) {
406: l.treeNodesChanged(event);
407: }
408: }
409:
410: /**
411: * Notifies the listeners that selection of the given
412: * {@code MatchingObject}'s has changed.
413: *
414: * @param matchingObj object's whose node's selection has changed
415: * @param includingSubnodes whether listeners should be notified also
416: * about change of the node's children's
417: * selection
418: * @see MatchingObj#markChildrenSelectionDirty()
419: */
420: void fireFileNodeSelectionChanged(MatchingObject matchingObj,
421: boolean includingSubnodes) {
422: assert EventQueue.isDispatchThread();
423:
424: if ((treeModelListeners == null)
425: || treeModelListeners.isEmpty()) {
426: return;
427: }
428:
429: final int index = resultModel.matchingObjects
430: .indexOf(matchingObj);
431:
432: /* Notify that the file node itself has changed... */
433: TreeModelEvent event = new TreeModelEvent(this , rootPath,
434: new int[] { index }, new Object[] { matchingObj });
435: for (TreeModelListener l : treeModelListeners) {
436: l.treeNodesChanged(event);
437: }
438:
439: if (includingSubnodes) {
440: /* ... and also all its children need to be updated: */
441: fireFileNodeChildrenSelectionChanged(matchingObj);
442: }
443: }
444:
445: /**
446: */
447: void fireFileNodeChildrenSelectionChanged(MatchingObject matchingObj) {
448: Node[] children = resultModel.basicCriteria
449: .getDetails(matchingObj.object);
450: int[] indices = new int[children.length];
451: for (int i = 0; i < indices.length; i++) {
452: indices[i] = i;
453: }
454: final TreeModelEvent event = new TreeModelEvent(this ,
455: new Object[] { getRoot(), matchingObj }, indices,
456: children);
457: for (TreeModelListener l : treeModelListeners) {
458: l.treeNodesChanged(event);
459: }
460: }
461:
462: /**
463: */
464: void fireDetailNodeSelectionChanged(MatchingObject matchingObj,
465: int index) {
466: assert EventQueue.isDispatchThread();
467:
468: if ((treeModelListeners == null)
469: || treeModelListeners.isEmpty()) {
470: return;
471: }
472:
473: int[] changedIndices = new int[] { index };
474: Node[] detailNodes = resultModel.basicCriteria
475: .getDetails(matchingObj.object);
476: Node[] changedNodes = (detailNodes.length == 1) ? detailNodes
477: : new Node[] { detailNodes[index] };
478: TreeModelEvent event = new TreeModelEvent(this , new Object[] {
479: getRoot(), matchingObj }, changedIndices, changedNodes);
480: for (TreeModelListener l : treeModelListeners) {
481: l.treeNodesChanged(event);
482: }
483: }
484:
485: /** Returns display name of the root node.
486: * @return display name of the root node.
487: */
488: @Override
489: public String toString() {
490: return super .toString() + "[" + rootDisplayName + "]"; // NOI18N
491: }
492: }
|