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:
042: package org.netbeans.modules.project.ui;
043:
044: import java.awt.BorderLayout;
045: import java.awt.Image;
046: import java.awt.Rectangle;
047: import java.awt.event.ActionEvent;
048: import java.beans.PropertyChangeEvent;
049: import java.beans.PropertyChangeListener;
050: import java.beans.PropertyVetoException;
051: import java.io.IOException;
052: import java.io.ObjectInput;
053: import java.io.ObjectOutput;
054: import java.util.ArrayList;
055: import java.util.Arrays;
056: import java.util.Enumeration;
057: import java.util.HashMap;
058: import java.util.Iterator;
059: import java.util.List;
060: import java.util.Map;
061: import javax.swing.AbstractAction;
062: import javax.swing.Action;
063: import javax.swing.ActionMap;
064: import javax.swing.SwingUtilities;
065: import javax.swing.text.DefaultEditorKit;
066: import javax.swing.tree.DefaultTreeModel;
067: import javax.swing.tree.TreeNode;
068: import javax.swing.tree.TreePath;
069: import javax.swing.tree.TreeModel;
070: import org.netbeans.api.project.FileOwnerQuery;
071: import org.netbeans.api.project.Project;
072: import org.netbeans.api.project.ProjectUtils;
073: import org.netbeans.api.project.ui.OpenProjects;
074: import org.netbeans.modules.project.ui.groups.Group;
075: import org.netbeans.spi.project.ActionProvider;
076: import org.openide.DialogDisplayer;
077: import org.openide.ErrorManager;
078: import org.openide.NotifyDescriptor;
079: import org.openide.awt.StatusDisplayer;
080: import org.openide.explorer.ExplorerManager;
081: import org.openide.explorer.ExplorerUtils;
082: import org.openide.explorer.view.BeanTreeView;
083: import org.openide.explorer.view.Visualizer;
084: import org.openide.filesystems.FileObject;
085: import org.openide.nodes.Node;
086: import org.openide.nodes.NodeNotFoundException;
087: import org.openide.nodes.NodeOp;
088: import org.openide.util.HelpCtx;
089: import org.openide.util.NbBundle;
090: import org.openide.util.RequestProcessor;
091: import org.openide.util.Utilities;
092: import org.openide.windows.TopComponent;
093: import org.openide.windows.WindowManager;
094:
095: /** TopComponment for viewing open projects.
096: * <P>
097: * PENEDING : Fix persistence when new Winsys allows
098: *
099: * @author Petr Hrebejk
100: */
101: public class ProjectTab extends TopComponent implements
102: ExplorerManager.Provider {
103:
104: public static final String ID_LOGICAL = "projectTabLogical_tc"; // NOI18N
105: public static final String ID_PHYSICAL = "projectTab_tc"; // NOI18N
106:
107: private static final Image ICON_LOGICAL = org.openide.util.Utilities
108: .loadImage("org/netbeans/modules/project/ui/resources/projectTab.png");
109: private static final Image ICON_PHYSICAL = org.openide.util.Utilities
110: .loadImage("org/netbeans/modules/project/ui/resources/filesTab.png");
111:
112: private static Map<String, ProjectTab> tabs = new HashMap<String, ProjectTab>();
113:
114: private transient final ExplorerManager manager;
115: private transient Node rootNode;
116:
117: private String id;
118: private transient final ProjectTreeView btv;
119:
120: public ProjectTab(String id) {
121: this ();
122: this .id = id;
123: initValues();
124: }
125:
126: public ProjectTab() {
127:
128: // See #36315
129: manager = new ExplorerManager();
130:
131: ActionMap map = getActionMap();
132: map.put(DefaultEditorKit.copyAction, ExplorerUtils
133: .actionCopy(manager));
134: map.put(DefaultEditorKit.cutAction, ExplorerUtils
135: .actionCut(manager));
136: map.put(DefaultEditorKit.pasteAction, ExplorerUtils
137: .actionPaste(manager));
138: map.put("delete", new DelegatingAction(
139: ActionProvider.COMMAND_DELETE, ExplorerUtils
140: .actionDelete(manager, true)));
141:
142: initComponents();
143:
144: btv = new ProjectTreeView(); // Add the BeanTreeView
145:
146: btv.setDragSource(true);
147:
148: btv.setRootVisible(false);
149:
150: add(btv, BorderLayout.CENTER);
151:
152: associateLookup(ExplorerUtils.createLookup(manager, map));
153:
154: }
155:
156: /**
157: * Update display to reflect {@link org.netbeans.modules.project.ui.groups.Group#getActiveGroup}.
158: * @param group current group, or null
159: */
160: public void setGroup(Group g) {
161: if (id.equals(ID_LOGICAL)) {
162: if (g != null) {
163: setName(NbBundle.getMessage(ProjectTab.class,
164: "LBL_projectTabLogical_tc_with_group", g
165: .getName()));
166: } else {
167: setName(NbBundle.getMessage(ProjectTab.class,
168: "LBL_projectTabLogical_tc"));
169: }
170: } else {
171: setName(NbBundle.getMessage(ProjectTab.class,
172: "LBL_projectTab_tc"));
173: }
174: // Seems to be useless: setToolTipText(getName());
175: }
176:
177: private void initValues() {
178: setGroup(Group.getActiveGroup());
179:
180: if (id.equals(ID_LOGICAL)) {
181: setIcon(ICON_LOGICAL);
182: } else {
183: setIcon(ICON_PHYSICAL);
184: }
185:
186: if (rootNode == null) {
187: // Create the node which lists open projects
188: rootNode = new ProjectsRootNode(
189: id.equals(ID_LOGICAL) ? ProjectsRootNode.LOGICAL_VIEW
190: : ProjectsRootNode.PHYSICAL_VIEW);
191: }
192: manager.setRootContext(rootNode);
193: }
194:
195: /** Explorer manager implementation
196: */
197: public ExplorerManager getExplorerManager() {
198: return manager;
199: }
200:
201: /* Singleton accessor. As ProjectTab is persistent singleton this
202: * accessor makes sure that ProjectTab is deserialized by window system.
203: * Uses known unique TopComponent ID TC_ID = "projectTab_tc" to get ProjectTab instance
204: * from window system. "projectTab_tc" is name of settings file defined in module layer.
205: * For example ProjectTabAction uses this method to create instance if necessary.
206: */
207: public static synchronized ProjectTab findDefault(String tcID) {
208:
209: ProjectTab tab = tabs.get(tcID);
210:
211: if (tab == null) {
212: //If settings file is correctly defined call of WindowManager.findTopComponent() will
213: //call TestComponent00.getDefault() and it will set static field component.
214:
215: TopComponent tc = WindowManager.getDefault()
216: .findTopComponent(tcID);
217: if (tc != null) {
218: if (!(tc instanceof ProjectTab)) {
219: //This should not happen. Possible only if some other module
220: //defines different settings file with the same name but different class.
221: //Incorrect settings file?
222: IllegalStateException exc = new IllegalStateException(
223: "Incorrect settings file. Unexpected class returned." // NOI18N
224: + " Expected:"
225: + ProjectTab.class.getName() // NOI18N
226: + " Returned:"
227: + tc.getClass().getName()); // NOI18N
228: ErrorManager.getDefault().notify(
229: ErrorManager.INFORMATIONAL, exc);
230: //Fallback to accessor reserved for window system.
231: tab = ProjectTab.getDefault(tcID);
232: } else {
233: tab = (ProjectTab) tc;
234: }
235: } else {
236: //This should not happen when settings file is correctly defined in module layer.
237: //TestComponent00 cannot be deserialized
238: //Fallback to accessor reserved for window system.
239: tab = ProjectTab.getDefault(tcID);
240: }
241: }
242: return tab;
243: }
244:
245: /* Singleton accessor reserved for window system ONLY. Used by window system to create
246: * ProjectTab instance from settings file when method is given. Use <code>findDefault</code>
247: * to get correctly deserialized instance of ProjectTab */
248: public static synchronized ProjectTab getDefault(String tcID) {
249:
250: ProjectTab tab = tabs.get(tcID);
251:
252: if (tab == null) {
253: tab = new ProjectTab(tcID);
254: tabs.put(tcID, tab);
255: }
256:
257: return tab;
258: }
259:
260: public static TopComponent getLogical() {
261: return getDefault(ID_LOGICAL);
262: }
263:
264: public static TopComponent getPhysical() {
265: return getDefault(ID_PHYSICAL);
266: }
267:
268: protected String preferredID() {
269: return id;
270: }
271:
272: public HelpCtx getHelpCtx() {
273: return ExplorerUtils.getHelpCtx(manager.getSelectedNodes(),
274: ID_LOGICAL.equals(id) ? new HelpCtx(
275: "ProjectTab_Projects") : new HelpCtx(
276: "ProjectTab_Files"));
277: }
278:
279: public int getPersistenceType() {
280: return TopComponent.PERSISTENCE_ALWAYS;
281: }
282:
283: // APPEARANCE
284:
285: /** This method is called from within the constructor to
286: * initialize the form.
287: * WARNING: Do NOT modify this code. The content of this method is
288: * always regenerated by the FormEditor.
289: */
290: private void initComponents() {//GEN-BEGIN:initComponents
291:
292: setLayout(new java.awt.BorderLayout());
293:
294: }//GEN-END:initComponents
295:
296: // Variables declaration - do not modify//GEN-BEGIN:variables
297: // End of variables declaration//GEN-END:variables
298:
299: @SuppressWarnings("deprecation")
300: public boolean requestFocusInWindow() {
301: super .requestFocusInWindow();
302: return btv.requestFocusInWindow();
303: }
304:
305: //#41258: In the SDI, requestFocus is called rather than requestFocusInWindow:
306: @SuppressWarnings("deprecation")
307: public void requestFocus() {
308: super .requestFocus();
309: btv.requestFocus();
310: }
311:
312: // PERSISTENCE
313:
314: private static final long serialVersionUID = 9374872358L;
315:
316: public void writeExternal(ObjectOutput out) throws IOException {
317: super .writeExternal(out);
318:
319: out.writeObject(id);
320: out.writeObject(rootNode.getHandle());
321: out.writeObject(btv.getExpandedPaths());
322: out.writeObject(getSelectedPaths());
323: }
324:
325: @SuppressWarnings("unchecked")
326: public void readExternal(ObjectInput in) throws IOException,
327: ClassNotFoundException {
328: super .readExternal(in);
329: id = (String) in.readObject();
330: rootNode = ((Node.Handle) in.readObject()).getNode();
331: List<String[]> exPaths = (List<String[]>) in.readObject();
332: List<String[]> selPaths = null;
333: try {
334: selPaths = (List<String[]>) in.readObject();
335: } catch (java.io.OptionalDataException e) {
336: // Sel paths missing
337: }
338: initValues();
339: // fix for #55701 (Expanding of previously expanded folder in explorer slows down startup)
340: // the expansion scales very bad now and can prolong startup up to several minutes
341: // disabling the expansion of nodes after start altogether
342: // (thus getting back to how it worked in NB 4.0 FCS, but letting the user turn it back on)
343: if (System.getProperty("netbeans.keep.expansion") != null) {
344: btv.expandNodes(exPaths);
345: selectPaths(selPaths);
346: }
347:
348: }
349:
350: // MANAGING ACTIONS
351:
352: protected void componentActivated() {
353: ExplorerUtils.activateActions(manager, true);
354: }
355:
356: protected void componentDeactivated() {
357: ExplorerUtils.activateActions(manager, false);
358: }
359:
360: // SEARCHING NODES
361:
362: // Called from the SelectNodeAction
363:
364: private final RequestProcessor RP = new RequestProcessor();
365:
366: public void selectNodeAsync(final FileObject object) {
367:
368: setCursor(Utilities.createProgressCursor(this ));
369: open();
370: requestActive();
371:
372: // Do it in different thread than AWT
373: RP.post(new Runnable() {
374: public void run() {
375: ProjectsRootNode root = (ProjectsRootNode) manager
376: .getRootContext();
377: Node tempNode = root.findNode(object);
378: if (tempNode == null) {
379: Project project = FileOwnerQuery.getOwner(object);
380: if (project != null
381: && !OpenProjectList.getDefault().isOpen(
382: project)) {
383: DialogDisplayer dd = DialogDisplayer
384: .getDefault();
385: String message = NbBundle.getMessage(
386: ProjectTab.class,
387: "MSG_openProject_confirm", //NOI18N
388: ProjectUtils.getInformation(project)
389: .getDisplayName());
390: String title = NbBundle.getMessage(
391: ProjectTab.class,
392: "MSG_openProject_confirm_title");//NOI18N
393: NotifyDescriptor.Confirmation confirm = new NotifyDescriptor.Confirmation(
394: message, title,
395: NotifyDescriptor.OK_CANCEL_OPTION);
396: DialogDisplayer.getDefault().notify(confirm);
397: if (confirm.getValue() == NotifyDescriptor.OK_OPTION) {
398: if (!OpenProjectList.getDefault().isOpen(
399: project)) {
400: OpenProjects.getDefault().open(
401: new Project[] { project },
402: false);
403: }
404: tempNode = root.findNode(object);
405: }
406: }
407: }
408: final Node selectedNode = tempNode;
409: // Back to AWT // Back to AWT
410: SwingUtilities.invokeLater(new Runnable() {
411: public void run() {
412: if (selectedNode != null) {
413: try {
414: manager
415: .setSelectedNodes(new Node[] { selectedNode });
416: btv.scrollToNode(selectedNode);
417: StatusDisplayer.getDefault()
418: .setStatusText(""); // NOI18N
419: } catch (PropertyVetoException e) {
420: // Bad day node found but can't be selected
421: }
422: } else {
423: StatusDisplayer
424: .getDefault()
425: .setStatusText(
426: NbBundle
427: .getMessage(
428: ProjectTab.class,
429: ID_LOGICAL
430: .equals(id) ? "MSG_NodeNotFound_ProjectsTab"
431: : "MSG_NodeNotFound_FilesTab")); // NOI18N
432: }
433: setCursor(null);
434: }
435: });
436: }
437: });
438:
439: }
440:
441: public boolean selectNode(FileObject object) {
442: // System.out.println("Selecting node " + id + " : " + object + " -AWT- " + SwingUtilities.isEventDispatchThread() );
443:
444: ProjectsRootNode root = (ProjectsRootNode) manager
445: .getRootContext();
446: Node selectedNode = root.findNode(object);
447: if (selectedNode != null) {
448: try {
449: manager.setSelectedNodes(new Node[] { selectedNode });
450: btv.scrollToNode(selectedNode);
451: return true;
452: } catch (PropertyVetoException e) {
453: // Bad day node found but can't be selected
454: return false;
455: }
456: }
457:
458: return false;
459: }
460:
461: public void expandNode(Node node) {
462: btv.expandNode(node);
463: }
464:
465: private List<String[]> getSelectedPaths() {
466: Node selectedNodes[] = manager.getSelectedNodes();
467: List<String[]> result = new ArrayList<String[]>();
468: Node rootNode = manager.getRootContext();
469:
470: for (int i = 0; i < selectedNodes.length; i++) {
471: String[] path = NodeOp.createPath(selectedNodes[i],
472: rootNode);
473: if (path != null) {
474: result.add(path);
475: }
476: }
477:
478: return result;
479: }
480:
481: private void selectPaths(List<String[]> paths) {
482:
483: if (paths == null) {
484: return;
485: }
486:
487: List<Node> selectedNodes = new ArrayList<Node>();
488:
489: Node rootNode = manager.getRootContext();
490:
491: for (Iterator<String[]> it = paths.iterator(); it.hasNext();) {
492: String[] sp = it.next();
493: try {
494: Node n = NodeOp.findPath(rootNode, sp);
495: if (n != null) {
496: selectedNodes.add(n);
497: }
498: } catch (NodeNotFoundException e) {
499: // Node wont be added
500: }
501: }
502:
503: if (!selectedNodes.isEmpty()) {
504: Node nodes[] = new Node[selectedNodes.size()];
505: selectedNodes.toArray(nodes);
506: try {
507: manager.setSelectedNodes(nodes);
508: } catch (PropertyVetoException e) {
509: // Bad day no selection change
510: }
511: }
512:
513: }
514:
515: // Private innerclasses ----------------------------------------------------
516:
517: /** Extending bean treeview. To be able to persist the selected paths
518: */
519: private class ProjectTreeView extends BeanTreeView {
520: public void scrollToNode(Node n) {
521: TreeNode tn = Visualizer.findVisualizer(n);
522: if (tn == null)
523: return;
524:
525: TreeModel model = tree.getModel();
526: if (!(model instanceof DefaultTreeModel))
527: return;
528:
529: TreePath path = new TreePath(((DefaultTreeModel) model)
530: .getPathToRoot(tn));
531: Rectangle r = tree.getPathBounds(path);
532: if (r != null)
533: tree.scrollRectToVisible(r);
534: }
535:
536: public List<String[]> getExpandedPaths() {
537:
538: List<String[]> result = new ArrayList<String[]>();
539:
540: TreeNode rtn = Visualizer.findVisualizer(rootNode);
541: TreePath tp = new TreePath(rtn); // Get the root
542:
543: for (Enumeration exPaths = tree.getExpandedDescendants(tp); exPaths != null
544: && exPaths.hasMoreElements();) {
545: TreePath ep = (TreePath) exPaths.nextElement();
546: Node en = Visualizer
547: .findNode(ep.getLastPathComponent());
548: String[] path = NodeOp.createPath(en, rootNode);
549:
550: // System.out.print("EXP "); ProjectTab.print( path );
551:
552: result.add(path);
553: }
554:
555: return result;
556:
557: }
558:
559: /** Expands all the paths, when exists
560: */
561: public void expandNodes(List exPaths) {
562:
563: for (Iterator it = exPaths.iterator(); it.hasNext();) {
564: String[] sp = (String[]) it.next();
565: TreePath tp = stringPath2TreePath(sp);
566:
567: if (tp != null) {
568: showPath(tp);
569: }
570: }
571: }
572:
573: /** Converts path of strings to TreePath if exists null otherwise
574: */
575: private TreePath stringPath2TreePath(String[] sp) {
576:
577: try {
578: Node n = NodeOp.findPath(rootNode, sp);
579:
580: // Create the tree path
581: TreeNode tns[] = new TreeNode[sp.length + 1];
582:
583: for (int i = sp.length; i >= 0; i--) {
584: if (n == null) { // Fix for 54832 it seems that sometimes
585: return null; // we get unparented node
586: }
587: tns[i] = Visualizer.findVisualizer(n);
588: n = n.getParentNode();
589: }
590: return new TreePath(tns);
591: } catch (NodeNotFoundException e) {
592: return null;
593: }
594: }
595:
596: }
597:
598: private class DelegatingAction extends AbstractAction implements
599: PropertyChangeListener {
600:
601: private Action explorerAction;
602: private String projectAction;
603:
604: public DelegatingAction(String projectAction,
605: Action explorerAction) {
606: this .projectAction = projectAction;
607: this .explorerAction = explorerAction;
608:
609: manager.addPropertyChangeListener(this );
610: explorerAction.addPropertyChangeListener(this );
611: }
612:
613: private boolean isProject() {
614: Node[] nodes = manager.getSelectedNodes();
615:
616: if (nodes.length == 1) {
617: return nodes[0].getParentNode() == rootNode;
618: }
619:
620: return false;
621: }
622:
623: public void actionPerformed(ActionEvent e) {
624: if (isProject()) {
625: Node[] nodes = manager.getSelectedNodes();
626: Project p = nodes[0].getLookup().lookup(Project.class);
627:
628: assert p != null;
629:
630: ActionProvider ap = p.getLookup().lookup(
631: ActionProvider.class);
632:
633: ap.invokeAction(projectAction, nodes[0].getLookup());
634: } else {
635: explorerAction.actionPerformed(e);
636: }
637: }
638:
639: public void updateIsEnabled() {
640: if (isProject()) {
641: Node[] nodes = manager.getSelectedNodes();
642: Project p = nodes[0].getLookup().lookup(Project.class);
643:
644: if (p == null) {
645: setEnabled(false);
646: return;
647: }
648:
649: ActionProvider ap = p.getLookup().lookup(
650: ActionProvider.class);
651:
652: //Fix for #60946: check whether the action is supported before asking whether it is enabled:
653: String[] sa = ap != null ? ap.getSupportedActions()
654: : new String[0];
655: int k = sa.length;
656:
657: for (int i = 0; i < k; i++) {
658: if (ActionProvider.COMMAND_DELETE.equals(sa[i])) {
659: setEnabled(ap.isActionEnabled(projectAction,
660: nodes[0].getLookup()));
661: return;
662: }
663: }
664:
665: setEnabled(false);
666: } else {
667: setEnabled(explorerAction.isEnabled());
668: }
669: }
670:
671: public void propertyChange(PropertyChangeEvent evt) {
672: //a bit brute force:
673: updateIsEnabled();
674: }
675:
676: }
677:
678: }
|