001: /*
002: * The contents of this file are subject to the terms of the Common Development
003: * and Distribution License (the License). You may not use this file except in
004: * compliance with the License.
005: *
006: * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
007: * or http://www.netbeans.org/cddl.txt.
008: *
009: * When distributing Covered Code, include this CDDL Header Notice in each file
010: * and include the License file at http://www.netbeans.org/cddl.txt.
011: * If applicable, add the following below the CDDL Header, with the fields
012: * enclosed by brackets [] replaced by your own identifying information:
013: * "Portions Copyrighted [year] [name of copyright owner]"
014: *
015: * The Original Software is NetBeans. The Initial Developer of the Original
016: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
017: * Microsystems, Inc. All Rights Reserved.
018: */
019:
020: package org.netbeans.modules.sql.project.ui;
021:
022: import java.awt.Component;
023: import java.awt.Image;
024: import java.beans.BeanInfo;
025: import java.beans.PropertyChangeEvent;
026: import java.beans.PropertyChangeListener;
027: import java.util.Collection;
028: import java.util.Enumeration;
029: import java.util.IdentityHashMap;
030: import java.util.Iterator;
031: import java.util.Map;
032: import java.util.SortedSet;
033: import java.util.TreeSet;
034: import java.util.Vector;
035: import javax.swing.ComboBoxModel;
036: import javax.swing.DefaultComboBoxModel;
037: import javax.swing.DefaultListCellRenderer;
038: import javax.swing.DefaultListModel;
039: import javax.swing.Icon;
040: import javax.swing.ImageIcon;
041: import javax.swing.JList;
042: import javax.swing.ListCellRenderer;
043: import org.netbeans.api.project.SourceGroup;
044: import org.netbeans.api.queries.VisibilityQuery;
045: import org.netbeans.modules.sql.project.base.PackageDisplayUtils;
046: import org.netbeans.modules.sql.project.base.PackageViewSettings;
047: import org.openide.filesystems.FileObject;
048: import org.openide.filesystems.FileUtil;
049: import org.openide.nodes.AbstractNode;
050: import org.openide.nodes.FilterNode;
051: import org.openide.nodes.Node;
052: import org.openide.util.WeakListeners;
053:
054: /**
055: * Factory for package views.
056: * @see org.netbeans.spi.project.ui.LogicalViewProvider
057: * @author Jesse Glick
058: */
059: public class PackageView {
060:
061: private PackageView() {
062: }
063:
064: /**
065: * Create a node which will contain package-oriented view of a source group.
066: * <p>
067: * The precise structure of this node is <em>not</em> specified by the API
068: * and is subject to arbitrary change (perhaps at user option).
069: * Callers should not make assumptions about the nature of subnodes, the
070: * code or display names of certain nodes, and so on. You may use cookies/lookup
071: * to find if particular subnodes correspond to folders or files.
072: * </p>
073: * @param group a source group which should be represented
074: * @return node which will display packages in given group
075: */
076: public static Node createPackageView(SourceGroup group) {
077: return new RootNode(group);
078: }
079:
080: /**
081: * Finds the node representing given object, if any.
082: * The current implementation works only for {@link org.openide.filesystems.FileObject}s
083: * and {@link org.openide.loaders.DataObject}s.
084: * @param rootNode a node some descendant of which should contain the object
085: * @param object object to find
086: * @return a node representing the given object, or null if no such node was found
087: */
088: public static Node findPath(Node rootNode, Object object) {
089:
090: PackageRootNode.PathFinder pf = (PackageRootNode.PathFinder) rootNode
091: .getLookup().lookup(PackageRootNode.PathFinder.class);
092:
093: if (pf != null) {
094: return pf.findPath(rootNode, object);
095: } else {
096: TreeRootNode.PathFinder pf2 = (TreeRootNode.PathFinder) rootNode
097: .getLookup().lookup(TreeRootNode.PathFinder.class);
098: if (pf2 != null) {
099: return pf2.findPath(rootNode, object);
100: } else {
101: return null;
102: }
103: }
104: }
105:
106: /**
107: * Create a list or combo box model suitable for {@link javax.swing.JList} from a source group
108: * showing all Java packages in the source group.
109: * To display it you will also need {@link #listRenderer}.
110: * <p>No particular guarantees are made as to the nature of the model objects themselves,
111: * except that {@link Object#toString} will give the fully-qualified package name
112: * (or <code>""</code> for the default package), regardless of what the renderer
113: * actually displays.</p>
114: * @param group a Java-like source group
115: * @return a model of its packages
116: * @since org.netbeans.modules.java.project/1 1.3
117: */
118:
119: public static ComboBoxModel createListView(SourceGroup group) {
120: TreeSet data = new TreeSet();
121: findNonExcludedPackages(data, group.getRootFolder(), group);
122: return new DefaultComboBoxModel(new Vector(data));
123: }
124:
125: /** Fills given collection with flatened packages under given folder
126: *@param target The collection to be filled
127: *@param fo The folder to be scanned
128: *@param group if null the collection will be filled with file objects if
129: * non null PackageItems will be created.
130: */
131: static void findNonExcludedPackages(PackageViewChildren children,
132: FileObject fo) {
133: findNonExcludedPackages(children, null, fo, null);
134: }
135:
136: static void findNonExcludedPackages(Collection target,
137: FileObject fo, SourceGroup group) {
138: findNonExcludedPackages(null, target, fo, group);
139: }
140:
141: private static void findNonExcludedPackages(
142: PackageViewChildren children, Collection target,
143: FileObject fo, SourceGroup group) {
144:
145: assert fo.isFolder() : "Package view only accepts folders"; // NOI18N
146:
147: if (!VisibilityQuery.getDefault().isVisible(fo)) {
148: return; // Don't show hidden packages
149: }
150:
151: FileObject[] kids = fo.getChildren();
152: boolean hasSubfolders = false;
153: boolean hasFiles = false;
154: for (int i = 0; i < kids.length; i++) {
155: // XXX could use PackageDisplayUtils.isSignificant here
156: if (VisibilityQuery.getDefault().isVisible(kids[i])) {
157: if (kids[i].isFolder()) {
158: findNonExcludedPackages(children, target, kids[i],
159: group);
160: hasSubfolders = true;
161: } else {
162: hasFiles = true;
163: }
164: }
165: }
166: if (hasFiles || !hasSubfolders) {
167: if (group != null) {
168: target.add(new PackageItem(group, fo, !hasFiles));
169: } else {
170: children.add(fo, !hasFiles);
171: }
172: }
173: }
174:
175: // public static ComboBoxModel createListView(SourceGroup group) {
176: // DefaultListModel model = new DefaultListModel();
177: // SortedSet/*<PackageItem>*/ items = new TreeSet();
178: // FileObject root = group.getRootFolder();
179: // if (PackageDisplayUtils.isSignificant(root)) {
180: // items.add(new PackageItem(group, root));
181: // }
182: // Enumeration/*<FileObject>*/ files = root.getChildren(true);
183: // while (files.hasMoreElements()) {
184: // FileObject f = (FileObject) files.nextElement();
185: // if (f.isFolder() && PackageDisplayUtils.isSignificant(f)) {
186: // items.add(new PackageItem(group, f));
187: // }
188: // }
189: // return new DefaultComboBoxModel(items.toArray(new PackageItem[items.size()]));
190: // }
191:
192: /**
193: * Create a renderer suited to rendering models created using {@link #createListView}.
194: * The exact nature of the display is not specified.
195: * Instances of String can also be rendered.
196: * @return a suitable package renderer
197: * @since org.netbeans.modules.java.project/1 1.3
198: */
199: public static ListCellRenderer listRenderer() {
200: return new PackageListCellRenderer();
201: }
202:
203: /**
204: * FilterNode which listens on the PackageViewSettings and changes the view to
205: * the package view or tree view
206: *
207: */
208: private static final class RootNode extends FilterNode implements
209: PropertyChangeListener {
210:
211: private SourceGroup sourceGroup;
212: private PackageViewSettings settings;
213:
214: private RootNode(SourceGroup group) {
215: super (getOriginalNode(group, PackageViewSettings
216: .getDefault()));
217: this .sourceGroup = group;
218: this .settings = PackageViewSettings.getDefault();
219: //this.settings.addPropertyChangeListener(WeakListeners.propertyChange(this, this.settings));
220: }
221:
222: public void propertyChange(PropertyChangeEvent event) {
223: if (PackageViewSettings.PROP_PACKAGE_VIEW_TYPE.equals(event
224: .getPropertyName())) {
225: changeOriginal(getOriginalNode(this .sourceGroup,
226: this .settings), true);
227: }
228: }
229:
230: private static Node getOriginalNode(SourceGroup group,
231: PackageViewSettings settings) {
232: assert settings != null : "PackageViewSettings can't be null"; //NOI18N
233: FileObject root = group.getRootFolder();
234: //Guard condition, if the project is (closed) and deleted but not yet gced
235: // and the view is switched, the source group is not valid.
236: if (root == null || !root.isValid()) {
237: return new AbstractNode(Children.LEAF);
238: }
239: switch (settings.getPackageViewType()) {
240: case PackageViewSettings.TYPE_PACKAGE_VIEW:
241: return new PackageRootNode(group);
242: case PackageViewSettings.TYPE_TREE:
243: return new TreeRootNode(group);
244: default:
245: assert false : "Unknown PackageView Type"; //NOI18N
246: return new PackageRootNode(group);
247: }
248: }
249: }
250:
251: /**
252: * Model item representing one package.
253: */
254: static final class PackageItem implements Comparable {
255:
256: private static IdentityHashMap/*<Image,Icon>*/image2icon = new IdentityHashMap();
257:
258: private final boolean empty;
259: private final FileObject pkg;
260: private final String pkgname;
261: private Icon icon;
262:
263: public PackageItem(SourceGroup group, FileObject pkg,
264: boolean empty) {
265: this .pkg = pkg;
266: this .empty = empty;
267: String path = FileUtil.getRelativePath(group
268: .getRootFolder(), pkg);
269: assert path != null : "No " + pkg + " in " + group;
270: pkgname = path.replace('/', '.');
271: }
272:
273: public String toString() {
274: return pkgname;
275: }
276:
277: public String getLabel() {
278: return PackageDisplayUtils.getDisplayLabel(pkgname);
279: }
280:
281: public Icon getIcon() {
282: if (icon == null) {
283: Image image = PackageDisplayUtils.getIcon(pkg, pkgname,
284: empty);
285: icon = (Icon) image2icon.get(image);
286: if (icon == null) {
287: icon = new ImageIcon(image);
288: image2icon.put(image, icon);
289: }
290: }
291: return icon;
292: }
293:
294: public int compareTo(Object obj) {
295: return pkgname.compareTo(((PackageItem) obj).pkgname);
296: }
297:
298: }
299:
300: /**
301: * The renderer which just displays {@link PackageItem#getLabel} and {@link PackageItem#getIcon}.
302: */
303: private static final class PackageListCellRenderer extends
304: DefaultListCellRenderer {
305:
306: public PackageListCellRenderer() {
307: }
308:
309: public Component getListCellRendererComponent(JList list,
310: Object value, int index, boolean isSelected,
311: boolean cellHasFocus) {
312: if (value instanceof PackageItem) {
313: PackageItem pkgitem = (PackageItem) value;
314: super .getListCellRendererComponent(list, pkgitem
315: .getLabel(), index, isSelected, cellHasFocus);
316: setIcon(pkgitem.getIcon());
317: } else {
318: // #49954: render a specially inserted package somehow.
319: String pkgitem = (String) value;
320: super.getListCellRendererComponent(list, pkgitem,
321: index, isSelected, cellHasFocus);
322: }
323: return this;
324: }
325:
326: }
327:
328: }
|