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-2006 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: package org.openide.explorer.view;
042:
043: import org.openide.explorer.*;
044: import org.openide.explorer.ExplorerManager.Provider;
045: import org.openide.nodes.Node;
046: import org.openide.nodes.Node.Property;
047:
048: import java.awt.*;
049:
050: import java.beans.*;
051: import java.util.logging.Logger;
052:
053: import javax.swing.*;
054: import javax.swing.tree.*;
055:
056: /** A view displaying tree of {@link Node}s but not showing its leaf nodes.
057: * Works well together (e.g. sharing one {@link ExplorerManager}) with {@link ListView}.
058: *
059: * <p>
060: * This class is a <q>view</q>
061: * to use it properly you need to add it into a component which implements
062: * {@link Provider}. Good examples of that can be found
063: * in {@link ExplorerUtils}. Then just use
064: * {@link Provider#getExplorerManager} call to get the {@link ExplorerManager}
065: * and control its state.
066: * </p>
067: * <p>
068: * There can be multiple <q>views</q> under one container implementing {@link Provider}. Select from
069: * range of predefined ones or write your own:
070: * </p>
071: * <ul>
072: * <li>{@link org.openide.explorer.view.BeanTreeView} - shows a tree of nodes</li>
073: * <li>{@link org.openide.explorer.view.ContextTreeView} - shows a tree of nodes without leaf nodes</li>
074: * <li>{@link org.openide.explorer.view.ListView} - shows a list of nodes</li>
075: * <li>{@link org.openide.explorer.view.IconView} - shows a rows of nodes with bigger icons</li>
076: * <li>{@link org.openide.explorer.view.ChoiceView} - creates a combo box based on the explored nodes</li>
077: * <li>{@link org.openide.explorer.view.TreeTableView} - shows tree of nodes together with a set of their {@link Property}</li>
078: * <li>{@link org.openide.explorer.view.MenuView} - can create a {@link JMenu} structure based on structure of {@link Node}s</li>
079: * </ul>
080: * <p>
081: * All of these views use {@link ExplorerManager#find} to walk up the AWT hierarchy and locate the
082: * {@link ExplorerManager} to use as a controler. They attach as listeners to
083: * it and also call its setter methods to update the shared state based on the
084: * user action. Not all views make sence together, but for example
085: * {@link org.openide.explorer.view.ContextTreeView} and {@link org.openide.explorer.view.ListView} were designed to complement
086: * themselves and behaves like windows explorer. The {@link org.openide.explorer.propertysheet.PropertySheetView}
087: * for example should be able to work with any other view.
088: * </p>
089: *
090: * @author Petr Hamernik
091: */
092: public class ContextTreeView extends TreeView {
093: /** generated Serialized Version UID */
094: static final long serialVersionUID = -8282594827988436813L;
095: /** logger to find out why the ContextTreeView tests fail so randomly */
096: static final Logger LOG = Logger.getLogger(ContextTreeView.class
097: .getName());
098:
099: /** Constructor.
100: */
101: public ContextTreeView() {
102: tree.getSelectionModel().setSelectionMode(
103: TreeSelectionModel.SINGLE_TREE_SELECTION);
104: }
105:
106: /* @return true if this TreeView accept the selected beans.
107: */
108: protected boolean selectionAccept(Node[] nodes) {
109: if (nodes.length == 0) {
110: return true;
111: }
112:
113: Node parent = nodes[0].getParentNode();
114:
115: for (int i = 1; i < nodes.length; i++) {
116: if (nodes[i].getParentNode() != parent) {
117: return false;
118: }
119: }
120:
121: return true;
122: }
123:
124: /* Called whenever the value of the selection changes.
125: * @param listSelectionEvent the event that characterizes the change.
126: */
127: protected void selectionChanged(Node[] nodes, ExplorerManager man)
128: throws PropertyVetoException {
129: if (nodes.length > 0) {
130: man.setExploredContext(nodes[0]);
131: }
132:
133: man.setSelectedNodes(nodes);
134: }
135:
136: /** Expand the given path and makes it visible.
137: * @param path the path
138: */
139: protected void showPath(TreePath path) {
140: tree.makeVisible(path);
141:
142: Rectangle rect = tree.getPathBounds(path);
143:
144: if (rect != null) {
145: rect.width += rect.x;
146: rect.x = 0;
147: tree.scrollRectToVisible(rect);
148: }
149:
150: tree.setSelectionPath(path);
151: }
152:
153: /** Shows selection to reflect the current state of the selection in the explorer.
154: *
155: * @param paths array of paths that should be selected
156: */
157: protected void showSelection(TreePath[] paths) {
158: if (paths.length == 0) {
159: tree.setSelectionPaths(new TreePath[0]);
160: } else {
161: tree.setSelectionPath(paths[0].getParentPath());
162: }
163: }
164:
165: /** Permit use of explored contexts.
166: *
167: * @return <code>true</code> always
168: */
169: protected boolean useExploredContextMenu() {
170: return true;
171: }
172:
173: /** Create model.
174: */
175: protected NodeTreeModel createModel() {
176: return new NodeContextModel();
177: }
178:
179: /** Excludes leafs from the model.
180: */
181: static final class NodeContextModel extends NodeTreeModel {
182: //
183: // Event filtering
184: //
185: private int[] newIndices;
186: private Object[] newChildren;
187:
188: public java.lang.Object getChild(java.lang.Object parent,
189: int index) {
190: int super Cnt = super .getChildCount(parent);
191: int myCnt = 0;
192:
193: for (int i = 0; i < super Cnt; i++) {
194: Object origChild = super .getChild(parent, i);
195: Node n = Visualizer.findNode(origChild);
196:
197: if (!n.isLeaf()) {
198: if (myCnt++ == index) {
199: return origChild;
200: }
201: }
202: }
203:
204: return null;
205: }
206:
207: public int getChildCount(java.lang.Object parent) {
208: int super Cnt = super .getChildCount(parent);
209: int myCnt = 0;
210:
211: for (int i = 0; i < super Cnt; i++) {
212: Node n = Visualizer.findNode(super .getChild(parent, i));
213:
214: if (!n.isLeaf()) {
215: myCnt++;
216: }
217: }
218:
219: return myCnt;
220: }
221:
222: public int getIndexOfChild(java.lang.Object parent,
223: java.lang.Object child) {
224: int super Cnt = super .getChildCount(parent);
225: int myCnt = 0;
226:
227: for (int i = 0; i < super Cnt; i++) {
228: Object origChild = super .getChild(parent, i);
229:
230: if (child.equals(origChild)) {
231: return myCnt;
232: }
233:
234: Node n = Visualizer.findNode(origChild);
235:
236: if (!n.isLeaf()) {
237: myCnt++;
238: }
239: }
240:
241: return -1;
242: }
243:
244: public boolean isLeaf(java.lang.Object node) {
245: return false;
246: }
247:
248: /** Filters given childIndices and children to contain only non-leafs
249: * return true if there is still something changed.
250: */
251: private boolean filterEvent(Object[] path, int[] childIndices,
252: Object[] children) {
253: assert (childIndices != null) && (children != null) : " ch: "
254: + children + " indices: " + childIndices; // NOI18N
255: assert children.length == childIndices.length : "They should be the same: "
256: + children.length + " == " + childIndices.length; // NOI18N
257: assert newChildren == null : "Children should be cleared: "
258: + newChildren; // NOI18N
259: assert newIndices == null : "indices should be cleared: "
260: + newIndices; // NOI18N
261: assert path.length > 0 : "Path has to be greater than zero "
262: + path.length; // NOI18N
263:
264: VisualizerNode parent = (VisualizerNode) path[path.length - 1];
265:
266: int[] filter = new int[childIndices.length];
267: int accepted = 0;
268:
269: for (int i = 0; i < childIndices.length; i++) {
270: VisualizerNode n = (VisualizerNode) children[i];
271:
272: if (!n.isLeaf()) {
273: filter[accepted++] = i;
274: }
275: }
276:
277: if (accepted == 0) {
278: return false;
279: }
280:
281: newIndices = new int[accepted];
282: newChildren = new Object[accepted];
283:
284: for (int i = 0; i < accepted; i++) {
285: newChildren[i] = children[filter[i]];
286: newIndices[i] = getIndexOfChild(parent, newChildren[i]);
287: }
288:
289: return true;
290: }
291:
292: /** Filters given childIndices and children to contain only non-leafs
293: * return true if there is still something changed.
294: */
295: private boolean removalEvent(Object[] path, int[] childIndices,
296: Object[] children) {
297: assert (childIndices != null) && (children != null) : " ch: "
298: + children + " indices: " + childIndices; // NOI18N
299: assert children.length == childIndices.length : "They should be the same: "
300: + children.length + " == " + childIndices.length; // NOI18N
301: assert newChildren == null : "Children should be cleared: "
302: + newChildren; // NOI18N
303: assert newIndices == null : "indices should be cleared: "
304: + newIndices; // NOI18N
305: assert path.length > 0 : "Path has to be greater than zero "
306: + path.length; // NOI18N
307:
308: VisualizerNode parent = (VisualizerNode) path[path.length - 1];
309:
310: int[] filter = new int[childIndices.length];
311: int accepted = 0;
312:
313: for (int i = 0; i < childIndices.length; i++) {
314: VisualizerNode n = (VisualizerNode) children[i];
315:
316: if (!n.isLeaf()) {
317: filter[accepted++] = i;
318: }
319: }
320:
321: if (accepted == 0) {
322: return false;
323: }
324:
325: newIndices = new int[accepted];
326: newChildren = new Object[accepted];
327:
328: int size = getChildCount(parent);
329: int index = 0;
330: int myPos = 0;
331: int actualI = 0;
332: int i = 0;
333:
334: for (int pos = 0; pos < accepted;) {
335: if (childIndices[index] <= i) {
336: VisualizerNode n = (VisualizerNode) children[index];
337:
338: if (!n.isLeaf()) {
339: newIndices[pos] = myPos++;
340: newChildren[pos] = n;
341: pos++;
342: }
343:
344: index++;
345: } else {
346: VisualizerNode n = (VisualizerNode) getChild(
347: parent, actualI++);
348:
349: if ((n != null) && !n.isLeaf()) {
350: myPos++;
351: }
352: }
353:
354: i++;
355: }
356:
357: return true;
358: }
359:
360: /* sends childIndices and children == null, no tranformation
361: protected void fireTreeStructureChanged (Object source, Object[] path, int[] childIndices, Object[] children) {
362: if (!filterEvent (childIndices, children)) return;
363: super.fireTreeStructureChanged(source, path, newIndices, newChildren);
364: newIndices = null;
365: newChildren = null;
366: }
367: */
368: protected void fireTreeNodesRemoved(Object source,
369: Object[] path, int[] childIndices, Object[] children) {
370: LOG.fine("fireTreeNodesRemoved"); // NOI18N
371: if (!removalEvent(path, childIndices, children)) {
372: LOG.fine("fireTreeNodesRemoved - exit"); // NOI18N
373: return;
374: }
375:
376: super .fireTreeNodesRemoved(source, path, newIndices,
377: newChildren);
378: newIndices = null;
379: newChildren = null;
380: LOG.fine("fireTreeNodesRemoved - end"); // NOI18N
381: }
382:
383: protected void fireTreeNodesInserted(Object source,
384: Object[] path, int[] childIndices, Object[] children) {
385: LOG.fine("fireTreeNodesInserted"); // NOI18N
386: if (!filterEvent(path, childIndices, children)) {
387: LOG.fine("fireTreeNodesInserted - exit"); // NOI18N
388: return;
389: }
390:
391: super .fireTreeNodesInserted(source, path, newIndices,
392: newChildren);
393: newIndices = null;
394: newChildren = null;
395: LOG.fine("fireTreeNodesInserted - end"); // NOI18N
396: }
397:
398: protected void fireTreeNodesChanged(Object source,
399: Object[] path, int[] childIndices, Object[] children) {
400: LOG.fine("fireTreeNodesChanged"); // NOI18N
401: if (!filterEvent(path, childIndices, children)) {
402: LOG.fine("fireTreeNodesChanged - exit"); // NOI18N
403: return;
404: }
405:
406: super .fireTreeNodesChanged(source, path, newIndices,
407: newChildren);
408: newIndices = null;
409: newChildren = null;
410: LOG.fine("fireTreeNodesChanged - end"); // NOI18N
411: }
412: }
413: // end of NodeContextModel
414: }
|