2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.spi.java.project.support.ui;
44
import java.awt.Component;
45
import java.awt.EventQueue;
46
import java.awt.Image;
47
import java.beans.PropertyChangeEvent;
48
import java.beans.PropertyChangeListener;
49
import java.util.ArrayList;
50
import java.util.Collection;
51
import java.util.IdentityHashMap;
52
import java.util.List;
54
import java.util.SortedSet;
55
import java.util.TreeSet;
56
import javax.swing.ComboBoxModel;
57
import javax.swing.DefaultComboBoxModel;
58
import javax.swing.Icon;
59
import javax.swing.ImageIcon;
60
import javax.swing.JLabel;
61
import javax.swing.JList;
62
import javax.swing.ListCellRenderer;
63
import javax.swing.plaf.UIResource;
64
import org.netbeans.api.progress.ProgressHandle;
65
import org.netbeans.api.progress.ProgressHandleFactory;
66
import org.netbeans.api.project.SourceGroup;
67
import org.netbeans.api.queries.VisibilityQuery;
68
import org.netbeans.modules.java.project.PackageDisplayUtils;
69
import org.netbeans.modules.java.project.JavaProjectSettings;
70
import org.openide.filesystems.FileObject;
71
import org.openide.filesystems.FileUtil;
72
import org.openide.nodes.AbstractNode;
73
import org.openide.nodes.FilterNode;
74
import org.openide.nodes.Node;
75
import org.openide.util.NbBundle;
76
import org.openide.util.WeakListeners;
79
* Factory for package views.
80
* @see org.netbeans.spi.project.ui.LogicalViewProvider
83
public class PackageView {
85
private PackageView() {}
88
* Create a node which will contain package-oriented view of a source group.
90
* The precise structure of this node is <em>not</em> specified by the API
91
* and is subject to arbitrary change (perhaps at user option).
92
* Callers should not make assumptions about the nature of subnodes, the
93
* code or display names of certain nodes, and so on. You may use cookies/lookup
94
* to find if particular subnodes correspond to folders or files.
96
* @param group a source group which should be represented
97
* @return node which will display packages in given group
99
public static Node createPackageView( SourceGroup group ) {
100
return new RootNode (group);
104
* Finds the node representing given object, if any.
105
* The current implementation works only for {@link org.openide.filesystems.FileObject}s
106
* and {@link org.openide.loaders.DataObject}s.
107
* @param rootNode a node some descendant of which should contain the object
108
* @param object object to find
109
* @return a node representing the given object, or null if no such node was found
111
public static Node findPath(Node rootNode, Object object) {
113
PackageRootNode.PathFinder pf = rootNode.getLookup().lookup(PackageRootNode.PathFinder.class);
116
return pf.findPath( rootNode, object );
118
TreeRootNode.PathFinder pf2 = rootNode.getLookup().lookup(TreeRootNode.PathFinder.class);
120
return pf2.findPath(rootNode, object);
128
* Create a list or combo box model suitable for {@link javax.swing.JList} from a source group
129
* showing all Java packages in the source group.
130
* To display it you will also need {@link #listRenderer}.
131
* <p>No particular guarantees are made as to the nature of the model objects themselves,
132
* except that {@link Object#toString} will give the fully-qualified package name
133
* (or <code>""</code> for the default package), regardless of what the renderer
134
* actually displays.</p>
135
* @param group a Java-like source group
136
* @return a model of its packages
137
* @since org.netbeans.modules.java.project/1 1.3
140
public static ComboBoxModel createListView(SourceGroup group) {
141
SortedSet<PackageItem> data = new TreeSet<PackageItem>();
142
findNonExcludedPackages(null, data, group.getRootFolder(), group, false);
143
return new DefaultComboBoxModel(data.toArray(new PackageItem[data.size()]));
146
/** Fills given collection with flattened packages under given folder
147
*@param target The collection to be filled
148
*@param fo The folder to be scanned
149
* @param group the group to scan
150
* @param createPackageItems if false the collection will be filled with file objects; if
151
* true PackageItems will be created.
152
* @param showProgress whether to show a progress handle or not
154
static void findNonExcludedPackages(PackageViewChildren children, Collection<PackageItem> target, FileObject fo, SourceGroup group, boolean showProgress) {
156
ProgressHandle progress = ProgressHandleFactory.createHandle(NbBundle.getMessage(PackageView.class, "PackageView.find_packages_progress", FileUtil.getFileDisplayName(fo)));
157
progress.start(1000);
158
findNonExcludedPackages(children, target, fo, group, progress, 0, 1000);
161
findNonExcludedPackages(children, target, fo, group, null, 0, 0);
165
private static void findNonExcludedPackages(PackageViewChildren children, Collection<PackageItem> target, FileObject fo, SourceGroup group, ProgressHandle progress, int start, int end) {
167
assert fo.isFolder() : "Package view only accepts folders"; // NOI18N
169
if (progress != null) {
170
String path = FileUtil.getRelativePath(children.getRoot(), fo);
171
assert path != null : fo + " in " + children.getRoot();
172
progress.progress(path.replace('/', '.'), start);
175
if ( !VisibilityQuery.getDefault().isVisible( fo ) ) {
176
return; // Don't show hidden packages
179
boolean hasSubfolders = false;
180
boolean hasFiles = false;
181
List<FileObject> folders = new ArrayList<FileObject>();
182
for (FileObject kid : fo.getChildren()) {
183
// XXX could use PackageDisplayUtils.isSignificant here
184
if (VisibilityQuery.getDefault().isVisible(kid) && group.contains(kid)) {
185
if (kid.isFolder()) {
187
hasSubfolders = true;
194
if (hasFiles || !hasSubfolders) {
195
if (target != null) {
196
target.add( new PackageItem(group, fo, !hasFiles ) );
200
children.add(fo, !hasFiles, false);
204
if (!folders.isEmpty()) {
205
int diff = (end - start) / folders.size();
207
for (FileObject kid : folders) {
208
// Do this after adding the parent, so we get a pre-order traversal.
209
// Also see PackageViewChildren.findChild: prefer to get root first.
210
findNonExcludedPackages(children, target, kid, group, progress, start + c * diff, start + (c + 1) * diff);
216
// public static ComboBoxModel createListView(SourceGroup group) {
217
// DefaultListModel model = new DefaultListModel();
218
// SortedSet/*<PackageItem>*/ items = new TreeSet();
219
// FileObject root = group.getRootFolder();
220
// if (PackageDisplayUtils.isSignificant(root)) {
221
// items.add(new PackageItem(group, root));
223
// Enumeration/*<FileObject>*/ files = root.getChildren(true);
224
// while (files.hasMoreElements()) {
225
// FileObject f = (FileObject) files.nextElement();
226
// if (f.isFolder() && PackageDisplayUtils.isSignificant(f)) {
227
// items.add(new PackageItem(group, f));
230
// return new DefaultComboBoxModel(items.toArray(new PackageItem[items.size()]));
235
* Create a renderer suited to rendering models created using {@link #createListView}.
236
* The exact nature of the display is not specified.
237
* Instances of String can also be rendered.
238
* @return a suitable package renderer
239
* @since org.netbeans.modules.java.project/1 1.3
241
public static ListCellRenderer listRenderer() {
242
return new PackageListCellRenderer();
246
* FilterNode which listens on the PackageViewSettings and changes the view to
247
* the package view or tree view
250
private static final class RootNode extends FilterNode implements PropertyChangeListener {
252
private SourceGroup sourceGroup;
254
private RootNode (SourceGroup group) {
255
super(getOriginalNode(group));
256
this.sourceGroup = group;
257
JavaProjectSettings.addPropertyChangeListener(WeakListeners.propertyChange(this, JavaProjectSettings.class));
258
group.addPropertyChangeListener(WeakListeners.propertyChange(this, group));
261
// XXX #98573: very crude, but what else to do? Want to call changeOriginal asynchronously.
262
// But this could randomly screw up tests - not just PackageViewTest, but maybe others too.
263
// (org.netbeans.modules.java.freeform.ui.ViewTest does not appear to be affected.)
264
private static boolean IN_UNIT_TEST = false;
267
Class.forName("junit.framework.TestCase");
269
} catch (ClassNotFoundException e) {}
271
public void propertyChange (PropertyChangeEvent event) {
272
String prop = event.getPropertyName();
273
if (JavaProjectSettings.PROP_PACKAGE_VIEW_TYPE.equals(prop) || SourceGroup.PROP_CONTAINERSHIP.equals(prop)) {
275
changeOriginal(getOriginalNode(sourceGroup), true);
277
EventQueue.invokeLater(new Runnable() {
279
changeOriginal(getOriginalNode(sourceGroup), true);
286
private static Node getOriginalNode(SourceGroup group) {
287
FileObject root = group.getRootFolder();
288
//Guard condition, if the project is (closed) and deleted but not yet gced
289
// and the view is switched, the source group is not valid.
290
if ( root == null || !root.isValid()) {
291
return new AbstractNode (Children.LEAF);
293
switch (JavaProjectSettings.getPackageViewType()) {
294
case JavaProjectSettings.TYPE_PACKAGE_VIEW:
295
return new PackageRootNode(group);
296
case JavaProjectSettings.TYPE_TREE:
297
return new TreeRootNode(group);
299
assert false : "Unknown PackageView Type"; //NOI18N
300
return new PackageRootNode(group);
306
* Model item representing one package.
308
static final class PackageItem implements Comparable<PackageItem> {
310
private static Map<Image,Icon> image2icon = new IdentityHashMap<Image,Icon>();
312
private final boolean empty;
313
private final FileObject pkg;
314
private final String pkgname;
317
public PackageItem(SourceGroup group, FileObject pkg, boolean empty) {
320
String path = FileUtil.getRelativePath(group.getRootFolder(), pkg);
321
assert path != null : "No " + pkg + " in " + group;
322
pkgname = path.replace('/', '.');
325
public String toString() {
329
public String getLabel() {
330
return PackageDisplayUtils.getDisplayLabel(pkgname);
333
public Icon getIcon() {
334
if ( icon == null ) {
335
Image image = PackageDisplayUtils.getIcon(pkg, pkgname, empty);
336
icon = image2icon.get(image);
337
if ( icon == null ) {
338
icon = new ImageIcon( image );
339
image2icon.put( image, icon );
345
public int compareTo(PackageItem p) {
346
return pkgname.compareTo(p.pkgname);
352
* The renderer which just displays {@link PackageItem#getLabel} and {@link PackageItem#getIcon}.
354
private static final class PackageListCellRenderer extends JLabel implements ListCellRenderer, UIResource {
356
public PackageListCellRenderer() {
360
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
361
// #93658: GTK needs name to render cell renderer "natively"
362
setName("ComboBox.listRenderer"); // NOI18N
364
if (value instanceof PackageItem) {
365
PackageItem pkgitem = (PackageItem) value;
366
setText(pkgitem.getLabel());
367
setIcon(pkgitem.getIcon());
369
// #49954: render a specially inserted package somehow.
370
String pkgitem = (String) value;
376
setBackground(list.getSelectionBackground());
377
setForeground(list.getSelectionForeground());
380
setBackground(list.getBackground());
381
setForeground(list.getForeground());
387
// #93658: GTK needs name to render cell renderer "natively"
388
public String getName() {
389
String name = super.getName();
390
return name == null ? "ComboBox.renderer" : name; // NOI18N