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): The Original Software is NetBeans. The Initial
025: * Developer of the Original Software is Sun Microsystems, Inc. Portions
026: * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
027: *
028: * If you wish your version of this file to be governed by only the CDDL
029: * or only the GPL Version 2, indicate your decision by adding
030: * "[Contributor] elects to include this software in this distribution
031: * under the [CDDL or GPL Version 2] license." If you do not indicate a
032: * single choice of license, a recipient has the option to distribute
033: * your version of this file under either the CDDL, the GPL Version 2 or
034: * to extend the choice of license to its licensees as provided above.
035: * However, if you add GPL Version 2 code and therefore, elected the GPL
036: * Version 2 license, then the option applies only if the new code is
037: * made subject to such option by the copyright holder.
038: */
039: package org.netbeans.modules.php.project;
040:
041: import java.beans.PropertyChangeEvent;
042: import java.beans.PropertyChangeListener;
043: import java.io.File;
044: import java.io.IOException;
045: import java.util.ArrayList;
046: import java.util.Collections;
047: import java.util.HashMap;
048: import java.util.Iterator;
049: import java.util.LinkedList;
050: import java.util.List;
051: import java.util.Map;
052: import java.util.StringTokenizer;
053: import java.util.logging.Logger;
054: import javax.swing.Action;
055: import javax.swing.event.ChangeEvent;
056: import javax.swing.event.ChangeListener;
057: import org.netbeans.api.project.ProjectUtils;
058: import org.netbeans.api.project.SourceGroup;
059: import org.netbeans.api.project.Sources;
060: import org.netbeans.modules.php.rt.spi.providers.Command;
061: import org.netbeans.modules.php.rt.spi.providers.CommandProvider;
062: import org.netbeans.modules.php.rt.spi.providers.WebServerProvider;
063: import org.netbeans.modules.php.rt.utils.PhpCommandUtils;
064: import org.netbeans.spi.project.SubprojectProvider;
065: import org.netbeans.spi.project.support.ant.AntProjectEvent;
066: import org.netbeans.spi.project.support.ant.AntProjectHelper;
067: import org.netbeans.spi.project.support.ant.AntProjectListener;
068: import org.netbeans.spi.project.ui.LogicalViewProvider;
069: import org.netbeans.spi.project.ui.support.CommonProjectActions;
070: import org.netbeans.spi.project.ui.support.ProjectSensitiveActions;
071: import org.netbeans.spi.viewmodel.Models;
072: import org.netbeans.spi.viewmodel.Models.ActionPerformer;
073: import org.openide.ErrorManager;
074: import org.openide.actions.CopyAction;
075: import org.openide.actions.CutAction;
076: import org.openide.actions.DeleteAction;
077: import org.openide.actions.FileSystemAction;
078: import org.openide.actions.FindAction;
079: import org.openide.actions.OpenAction;
080: import org.openide.actions.PasteAction;
081: import org.openide.actions.RenameAction;
082: import org.openide.actions.SaveAsTemplateAction;
083: import org.openide.actions.ToolsAction;
084: import org.openide.filesystems.FileAttributeEvent;
085: import org.openide.filesystems.FileChangeListener;
086: import org.openide.filesystems.FileEvent;
087: import org.openide.filesystems.FileObject;
088: import org.openide.filesystems.FileRenameEvent;
089: import org.openide.filesystems.FileUtil;
090: import org.openide.loaders.DataFilter;
091: import org.openide.loaders.DataFolder;
092: import org.openide.loaders.DataObject;
093: import org.openide.nodes.AbstractNode;
094: import org.openide.nodes.Children;
095: import org.openide.nodes.FilterNode;
096: import org.openide.nodes.Node;
097: import org.openide.nodes.NodeNotFoundException;
098: import org.openide.nodes.NodeOp;
099: import org.openide.util.HelpCtx;
100: import org.openide.util.NbBundle;
101: import org.openide.util.WeakListeners;
102: import org.openide.util.actions.SystemAction;
103: import org.openide.util.lookup.Lookups;
104:
105: /**
106: * @author ads
107: */
108: class PhpLogicalViewProvider implements LogicalViewProvider,
109: AntProjectListener {
110:
111: static final String SOURCE_ROOT_NODE_NAME = "LBL_PhpFiles";
112:
113: PhpLogicalViewProvider(PhpProject project,
114: SubprojectProvider provider) {
115: myProject = project;
116: mySubProjectProvider = provider;
117: myActionsByCommand = new HashMap<Command, Action>();
118: getProject().getHelper().addAntProjectListener(this );
119: }
120:
121: /*
122: * (non-Javadoc)
123: *
124: * @see org.netbeans.spi.project.ui.LogicalViewProvider#createLogicalView()
125: */
126: public Node createLogicalView() {
127: return new PhpLogicalViewRootNode();
128: }
129:
130: /*
131: * (non-Javadoc)
132: *
133: * @see org.netbeans.spi.project.ui.LogicalViewProvider#findPath(org.openide.nodes.Node,
134: * java.lang.Object)
135: */
136: @SuppressWarnings(value="unchecked")
137: public Node findPath(Node root, Object target) {
138: // Check each child node in turn.
139: Node[] children = root.getChildren().getNodes(true);
140: for (Node node : children) {
141: if (target instanceof DataObject
142: || target instanceof FileObject) {
143: DataObject d = node.getLookup()
144: .lookup(DataObject.class);
145: if (d == null) {
146: continue;
147: }
148: // Copied from
149: // org.netbeans.spi.java.project.support.ui.TreeRootNode.PathFinder.findPath:
150: FileObject kidFO = d.getPrimaryFile();
151: FileObject targetFO = target instanceof DataObject ? ((DataObject) target)
152: .getPrimaryFile()
153: : (FileObject) target;
154: if (kidFO == targetFO) {
155: return node;
156: } else if (FileUtil.isParentOf(kidFO, targetFO)) {
157: String relPath = FileUtil.getRelativePath(kidFO,
158: targetFO);
159: List path = Collections
160: .list(new /*<String>*/StringTokenizer(
161: relPath, "/")); // NOI18N
162: // XXX see original code for justification
163: path.set(path.size() - 1, targetFO.getName());
164: try {
165: Node found = NodeOp.findPath(node, Collections
166: .enumeration(path));
167:
168: if (hasObject(found, target)) {
169: return found;
170: }
171: Node parent = found.getParentNode();
172: Children kids = parent.getChildren();
173: children = kids.getNodes();
174: for (Node child : children) {
175: if (hasObject(child, target)) {
176: return child;
177: }
178: }
179: } catch (NodeNotFoundException e) {
180: return null;
181: }
182: }
183: }
184: }
185: return null;
186: }
187:
188: /* (non-Javadoc)
189: * @see org.netbeans.spi.project.support.ant.AntProjectListener#configurationXmlChanged(org.netbeans.spi.project.support.ant.AntProjectEvent)
190: */
191: public void configurationXmlChanged(AntProjectEvent event) {
192: myActionsByCommand.clear();
193: }
194:
195: /* (non-Javadoc)
196: * @see org.netbeans.spi.project.support.ant.AntProjectListener#propertiesChanged(org.netbeans.spi.project.support.ant.AntProjectEvent)
197: */
198: public void propertiesChanged(AntProjectEvent arg0) {
199: myActionsByCommand.clear();
200: }
201:
202: private boolean hasObject(Node node, Object obj) {
203: if (obj == null) {
204: return false;
205: }
206: DataObject dataObject = node.getLookup().lookup(
207: DataObject.class);
208: if (dataObject == null) {
209: return false;
210: }
211: if (obj instanceof DataObject) {
212: if (dataObject.equals(obj)) {
213: return true;
214: }
215: FileObject fileObject = ((DataObject) obj).getPrimaryFile();
216: return hasObject(node, fileObject);
217: } else if (obj instanceof FileObject) {
218: FileObject fileObject = dataObject.getPrimaryFile();
219: return obj.equals(fileObject);
220: } else {
221: return false;
222: }
223: }
224:
225: private PhpProject getProject() {
226: return myProject;
227: }
228:
229: private SubprojectProvider getSubProjectProvider() {
230: return mySubProjectProvider;
231: }
232:
233: private class PhpLogicalViewRootNode extends AbstractNode {
234:
235: private PhpLogicalViewRootNode() {
236: super (new LogicalViewChildren(), Lookups
237: .fixed(new Object[] { getProject() }));
238:
239: setIconBaseWithExtension(ResourceMarker.getLocation()
240: + ResourceMarker.PROJECT_ICON);
241:
242: setName(ProjectUtils.getInformation(myProject)
243: .getDisplayName());
244: }
245:
246: @Override
247: public Action[] getActions(boolean context) {
248: if (context) {
249: return super .getActions(context);
250: } else {
251: return getAdditionalActions();
252: }
253: }
254:
255: private Action[] getAdditionalActions() {
256: List<Action> list = new LinkedList<Action>();
257:
258: list.add(0, CommonProjectActions.newFileAction());
259:
260: // actions from provider
261: for (Action action : getProviderActions()) {
262: list.add(action);
263: }
264:
265: // get our project actions
266: for (Action action : getProjectActions()) {
267: list.add(action);
268: }
269:
270: // get standard project actions
271: for (Action action : getStandardProjectActions()) {
272: list.add(action);
273: }
274:
275: return list.toArray(new Action[list.size()]);
276: }
277:
278: private Action[] getProjectActions() {
279: List<Action> list = new LinkedList<Action>();
280:
281: PhpActionProvider actionProvider = getProject().getLookup()
282: .lookup(PhpActionProvider.class);
283: for (Command command : actionProvider.getProjectCommands()) {
284: String id = command.getId();
285: String label = command.getLabel();
286: list.add(ProjectSensitiveActions.projectCommandAction(
287: id, label, null));
288: }
289:
290: return list.toArray(new Action[] {});
291: }
292:
293: private Action[] getStandardProjectActions() {
294: // do not get keysList from actionProvider.getStandardProjectCommands()
295: // because it will give only keysList of commands that we support outselves
296: // and will not allow desired formatting
297: return new Action[] { null,
298: CommonProjectActions.setAsMainProjectAction(),
299: CommonProjectActions.openSubprojectsAction(),
300: CommonProjectActions.closeProjectAction(), null,
301: CommonProjectActions.renameProjectAction(),
302: CommonProjectActions.moveProjectAction(),
303: CommonProjectActions.copyProjectAction(),
304: CommonProjectActions.deleteProjectAction(), null,
305: CommonProjectActions.customizeProjectAction() };
306:
307: }
308:
309: @Override
310: public HelpCtx getHelpCtx() {
311: return new HelpCtx(PhpLogicalViewProvider.class);
312: }
313: }
314:
315: private class LogicalViewChildren extends Children.Keys implements
316: FileChangeListener, ChangeListener, PropertyChangeListener
317: //,FileStatusListener
318: {
319:
320: private ChangeListener sourcesListener;
321: private java.util.Map groupsListeners;
322:
323: //private HashMap<FileSystem, FileStatusListener> fileSystemListeners;
324:
325: /* (non-Javadoc)
326: * @see org.openide.nodes.Children#addNotify()
327: */
328: @Override
329: protected void addNotify() {
330: super .addNotify();
331: getProject().getHelper().getProjectDirectory()
332: .addFileChangeListener(this );
333: createNodes();
334: }
335:
336: /* (non-Javadoc)
337: * @see org.openide.nodes.Children#removeNotify()
338: */
339: @Override
340: protected void removeNotify() {
341: setKeys(Collections.EMPTY_SET);
342: getProject().getHelper().getProjectDirectory()
343: .removeFileChangeListener(this );
344: super .removeNotify();
345: }
346:
347: /* (non-Javadoc)
348: * @see org.openide.nodes.Children.Keys#createNodes(java.lang.Object)
349: */
350: @Override
351: protected Node[] createNodes(Object key) {
352: Node node = null;
353: if (key instanceof SourceGroup) {
354: SourceGroup sourceGroup = (SourceGroup) key;
355: DataFolder folder = getFolder(sourceGroup
356: .getRootFolder());
357: if (folder != null) {
358: /* no need to use sourceGroup.getDisplayName() while we have only one sourceRoot.
359: * Now it contains not good-looking label.
360: * We put label there in PhpSources.configureSources()
361: */
362: //node = new SrcNode(folder, sourceGroup.getDisplayName());
363: node = new SrcNode(folder);
364: }
365: }
366: return node == null ? new Node[] {} : new Node[] { node };
367: }
368:
369: /* (non-Javadoc)
370: * @see org.openide.filesystems.FileChangeListener#fileAttributeChanged(org.openide.filesystems.FileAttributeEvent)
371: */
372: public void fileAttributeChanged(FileAttributeEvent arg0) {
373: }
374:
375: /* (non-Javadoc)
376: * @see org.openide.filesystems.FileChangeListener#fileChanged(org.openide.filesystems.FileEvent)
377: */
378: public void fileChanged(FileEvent arg0) {
379: }
380:
381: /* (non-Javadoc)
382: * @see org.openide.filesystems.FileChangeListener#fileDataCreated(org.openide.filesystems.FileEvent)
383: */
384: public void fileDataCreated(FileEvent arg0) {
385: }
386:
387: /* (non-Javadoc)
388: * @see org.openide.filesystems.FileChangeListener#fileDeleted(org.openide.filesystems.FileEvent)
389: */
390: public void fileDeleted(FileEvent arg0) {
391: }
392:
393: /* (non-Javadoc)
394: * @see org.openide.filesystems.FileChangeListener#fileFolderCreated(org.openide.filesystems.FileEvent)
395: */
396: public void fileFolderCreated(FileEvent arg0) {
397: // is it useful for us? looks like copied from Enterprise/bpel
398: // should invoke createNodes() only if updated file is not a source file
399: //createNodes();
400: }
401:
402: /* (non-Javadoc)
403: * @see org.openide.filesystems.FileChangeListener#fileRenamed(org.openide.filesystems.FileRenameEvent)
404: */
405: public void fileRenamed(FileRenameEvent arg0) {
406: // is it useful for us? looks like copied from Enterprise/bpel
407: // should invoke createNodes() only if updated file is not a source file
408: //createNodes();
409: }
410:
411: /*
412: * @see javax.swing.event.ChangeListener(javax.swing.event.ChangeEvent)
413: * sources change
414: */
415: public void stateChanged(ChangeEvent e) {
416: createNodes();
417: }
418:
419: /*
420: * source group change
421: */
422: public void propertyChange(PropertyChangeEvent evt) {
423: String property = evt.getPropertyName();
424: if (property.startsWith(PhpProject.SRC_)
425: && property.endsWith(PhpProject._DIR)) {
426: createNodes();
427: }
428: }
429:
430: /*
431: * @see org.openide.filesystems.FileStatusListener#annotationChanged(org.openide.filesystems.FileStatusEvent)
432: * file system change
433: */
434: /*
435: public void annotationChanged(FileStatusEvent ev) {
436: createNodes();
437: }
438: */
439:
440: private void createNodes() {
441: // update Sources listeners
442: Sources sources = ProjectUtils.getSources(myProject);
443: updateSourceListeners(sources);
444:
445: // parse SG
446: // update SG listeners
447: // TODO check if this is necessary
448: final SourceGroup[] sourceGroups = Utils
449: .getSourceGroups(myProject);
450: updateSourceGroupsListeners(sourceGroups);
451: final SourceGroup[] groups = new SourceGroup[sourceGroups.length];
452: System.arraycopy(sourceGroups, 0, groups, 0,
453: sourceGroups.length);
454:
455: List<Object> keysList = new ArrayList<Object>(groups.length);
456: //Set<FileObject> roots = new HashSet<FileObject>();
457: FileObject fileObject = null;
458: for (int i = 0; i < groups.length; i++) {
459: fileObject = groups[i].getRootFolder();
460: DataFolder srcDir = getFolder(fileObject);
461:
462: if (srcDir != null) {
463: keysList.add(groups[i]);
464: }
465: //roots.add(fileObject);
466: }
467: if (keysList.size() > 0) {
468: setKeys(keysList);
469: }
470: // Seems that we do not need to implement FileStatusListener
471: // to listen to source groups root folders changes.
472: // look at RubyLogicalViewRootNode for example.
473: //updateSourceRootsListeners(roots);
474: }
475:
476: private void updateSourceListeners(Sources sources) {
477: if (sourcesListener == null) {
478: sourcesListener = WeakListeners.change(this , sources);
479: sources.addChangeListener(sourcesListener);
480: }
481: }
482:
483: private void updateSourceGroupsListeners(
484: SourceGroup[] sourceGroups) {
485: if (groupsListeners != null) {
486: Iterator it = groupsListeners.keySet().iterator();
487: while (it.hasNext()) {
488: SourceGroup group = (SourceGroup) it.next();
489: PropertyChangeListener pcl = (PropertyChangeListener) groupsListeners
490: .get(group);
491: group.removePropertyChangeListener(pcl);
492: }
493: }
494: groupsListeners = new HashMap();
495: for (SourceGroup group : sourceGroups) {
496: PropertyChangeListener pcl = WeakListeners
497: .propertyChange(this , group);
498: groupsListeners.put(group, pcl);
499: group.addPropertyChangeListener(pcl);
500: }
501: }
502:
503: /*
504: private void updateSourceRootsListeners(Set<FileObject> files) {
505: if (fileSystemListeners != null) {
506: Iterator it = fileSystemListeners.keySet().iterator();
507: while (it.hasNext()) {
508: FileSystem fs = (FileSystem) it.next();
509: FileStatusListener fsl = fileSystemListeners.get(fs);
510: fs.removeFileStatusListener(fsl);
511: }
512: }
513:
514: fileSystemListeners = new HashMap<FileSystem, FileStatusListener>();
515: if (files == null) {
516: return;
517: }
518:
519: Iterator it = files.iterator();
520: Set<FileSystem> hookedFileSystems = new HashSet<FileSystem>();
521: while (it.hasNext()) {
522: FileObject fo = (FileObject) it.next();
523: try {
524: FileSystem fs = fo.getFileSystem();
525: if (hookedFileSystems.contains(fs)) {
526: continue;
527: }
528: hookedFileSystems.add(fs);
529: FileStatusListener fsl = FileUtil.weakFileStatusListener(this, fs);
530: fs.addFileStatusListener(fsl);
531: fileSystemListeners.put(fs, fsl);
532: } catch (FileStateInvalidException e) {
533: ErrorManager err = ErrorManager.getDefault();
534: err.annotate(e, ErrorManager.UNKNOWN, "Cannot get " + fo + " filesystem, ignoring...", null, null, null); // NOI18N
535: err.notify(ErrorManager.INFORMATIONAL, e);
536: }
537: }
538: }
539: */
540:
541: private DataFolder getFolder(String propName) {
542: String propertyValue = getProject().getEvaluator()
543: .getProperty(propName);
544: if (propertyValue != null) {
545: FileObject fileObject = getProject().getHelper()
546: .resolveFileObject(propertyValue);
547: return getFolder(fileObject);
548: }
549: return null;
550: }
551:
552: private DataFolder getFolder(FileObject fileObject) {
553: if (fileObject != null && fileObject.isValid()) {
554: try {
555: DataFolder dataFolder = DataFolder
556: .findFolder(fileObject);
557: return dataFolder;
558: } catch (Exception ex) {
559: ErrorManager.getDefault().notify(
560: ErrorManager.INFORMATIONAL, ex);
561: }
562: }
563: return null;
564: }
565:
566: }
567:
568: private class SrcNode extends FilterNode {
569:
570: /**
571: * creates source root node based on specified DataFolder.
572: * Name is taken from bundle by SOURCE_ROOT_NODE_NAME key.
573: * <br/>
574: * TODO : if we support several source roots, remove this constructor
575: */
576: SrcNode(DataFolder folder) {
577: this (folder, NbBundle
578: .getMessage(PhpLogicalViewProvider.class,
579: SOURCE_ROOT_NODE_NAME));
580: }
581:
582: /**
583: * creates source root node based on specified DataFolder.
584: * Uses specified name.
585: */
586: SrcNode(DataFolder folder, String name) {
587: this (new FilterNode(folder.getNodeDelegate(), folder
588: .createNodeChildren(new PhpSourcesFilter())), name);
589: }
590:
591: private SrcNode(FilterNode node, String name) {
592: super (node, new FolderChildren(node));
593: disableDelegation(DELEGATE_GET_DISPLAY_NAME
594: | DELEGATE_SET_DISPLAY_NAME
595: | DELEGATE_GET_SHORT_DESCRIPTION
596: | DELEGATE_GET_ACTIONS);
597: setDisplayName(name);
598: }
599:
600: @Override
601: public boolean canCopy() {
602: return false;
603: }
604:
605: @Override
606: public boolean canCut() {
607: return false;
608: }
609:
610: @Override
611: public boolean canRename() {
612: return false;
613: }
614:
615: @Override
616: public boolean canDestroy() {
617: return false;
618: }
619:
620: @Override
621: public Action[] getActions(boolean context) {
622: if (context) {
623: return super .getActions(context);
624: } else {
625: return getAdditionalActions();
626: }
627: }
628:
629: private Action[] getAdditionalActions() {
630: List<Action> list = new LinkedList<Action>();
631:
632: list.add(0, CommonProjectActions.newFileAction());
633:
634: // the same provider actions as for project
635: for (Action action : getProviderActions()) {
636: list.add(action);
637: }
638:
639: for (Action action : getSrcActions()) {
640: list.add(action);
641: }
642:
643: for (Action action : getStandardSrcActions()) {
644: list.add(action);
645: }
646:
647: return list.toArray(new Action[list.size()]);
648: }
649:
650: private Action[] getSrcActions() {
651: ImportCommand importComm = new ImportCommand(getProject());
652: RunLocalCommand runLocalComm = new RunLocalCommand(
653: getProject());
654:
655: Action[] actions = new Action[] {
656: ProjectSensitiveActions.projectCommandAction(
657: importComm.getId(), importComm.getLabel(),
658: null),
659: ProjectSensitiveActions.projectCommandAction(
660: runLocalComm.getId(), runLocalComm
661: .getLabel(), null) };
662: return actions;
663: }
664:
665: private Action[] getStandardSrcActions() {
666: Action[] actions = new Action[] { null,
667: SystemAction.get(FindAction.class), null,
668: SystemAction.get(PasteAction.class), null,
669: SystemAction.get(FileSystemAction.class),
670: SystemAction.get(ToolsAction.class), };
671: return actions;
672: }
673: }
674:
675: /**
676: * Children for node that represents folder (SrcNode or PackageNode)
677: */
678: private class FolderChildren extends FilterNode.Children {
679:
680: FolderChildren(final Node originalNode) {
681: super (originalNode);
682: }
683:
684: @Override
685: protected Node[] createNodes(Node key) {
686: return super .createNodes(key);
687: }
688:
689: @Override
690: protected Node copyNode(final Node originalNode) {
691: DataObject dobj = originalNode.getLookup().lookup(
692: DataObject.class);
693: return (dobj instanceof DataFolder) ? new PackageNode(
694: originalNode) : new ObjectNode(originalNode);
695: }
696: }
697:
698: private final class PackageNode extends FilterNode {
699:
700: public PackageNode(final Node originalNode) {
701: super (originalNode, new FolderChildren(originalNode));
702: }
703:
704: @Override
705: public Action[] getActions(boolean context) {
706: if (context) {
707: return super .getActions(context);
708: } else {
709: return getAdditionalActions();
710: }
711:
712: }
713:
714: private Action[] getAdditionalActions() {
715: if (myActions == null) {
716: List<Action> actions = new LinkedList<Action>();
717:
718: for (Action action : super .getActions(false)) {
719: actions.add(action);
720: }
721:
722: // want to add recent after 'NewFile' action.
723: // use fixed index not to search for 'NewFile'
724: // in seper actions each time (this wuill need to create
725: // CommonProjectActions.newFileAction() and check equals() )
726: int pos = 2;
727: for (Action action : getProviderActions()) {
728: actions.add(pos++, action);
729: }
730:
731: for (Action action : getFolderActions()) {
732: actions.add(pos++, action);
733: }
734:
735: myActions = actions.toArray(new Action[] {});
736: }
737: return myActions;
738: }
739:
740: private Action[] getFolderActions() {
741: RunLocalCommand runCommand = new RunLocalCommand(
742: getProject());
743: Action runAction = getWrapperAction(runCommand);
744:
745: Action[] actions = new Action[] { runAction, null,
746: SystemAction.get(FileSystemAction.class) };
747: return actions;
748: }
749:
750: Action[] myActions;
751:
752: }
753:
754: private boolean isInvokedForProject() {
755: return PhpCommandUtils.isInvokedForProject();
756: }
757:
758: private boolean isInvokedForSrcRoot() {
759: return PhpCommandUtils.isInvokedForSrcRoot();
760: }
761:
762: /**
763: * creates Action object for given Command.
764: * Uses ProjectSensitiveActions.projectCommandAction()
765: * if Project is among selected nodes,
766: * Creates AbstractAction otherwise.
767: */
768: private Action getWrapperAction(Command command) {
769: Action action = null;
770: if (isInvokedForProject() || isInvokedForSrcRoot()) {
771: action = ProjectSensitiveActions.projectCommandAction(
772: command.getId(), command.getLabel(), null);
773: } else {
774: action = myActionsByCommand.get(command);
775: if (action == null) {
776: action = createWrapperAction(command);
777: myActionsByCommand.put(command, action);
778: }
779: }
780: return action;
781: }
782:
783: private Action createWrapperAction(final Command command) {
784: Action action = Models.createAction(command.getLabel(),
785: new ActionPerformer() {
786:
787: public boolean isEnabled(Object node) {
788: return command.isEnabled();
789: }
790:
791: public void perform(Object[] nodes) {
792: PhpActionProvider.PhpCommandRunner
793: .runCommand(command);
794: }
795: }, Models.MULTISELECTION_TYPE_ANY);
796: return action;
797: }
798:
799: private final class ObjectNode extends FilterNode {
800:
801: public ObjectNode(final Node originalNode) {
802: super (originalNode);
803: }
804:
805: @Override
806: public Action[] getActions(boolean context) {
807: if (context) {
808: return super .getActions(context);
809: } else {
810: return getAdditionalActions();
811: }
812: }
813:
814: private Action[] getAdditionalActions() {
815: if (myActions == null) {
816: List<Action> actions = new LinkedList<Action>();
817:
818: actions.add(SystemAction.get(OpenAction.class));
819: actions.add(null);
820:
821: for (Action action : getProviderActions()) {
822: actions.add(action);
823: }
824:
825: for (Action action : getObjectActions()) {
826: actions.add(action);
827: }
828:
829: for (Action action : super .getActions(false)) {
830: actions.add(action);
831: }
832:
833: myActions = actions.toArray(new Action[] {});
834: }
835:
836: return myActions;
837: }
838:
839: private Action[] getObjectActions() {
840: RunLocalCommand runCommand = new RunLocalCommand(
841: getProject());
842: Action runAction = getWrapperAction(runCommand);
843:
844: Action[] actions = new Action[] { runAction, null,
845: SystemAction.get(CutAction.class),
846: SystemAction.get(CopyAction.class),
847: SystemAction.get(PasteAction.class), null,
848: SystemAction.get(DeleteAction.class),
849: SystemAction.get(RenameAction.class), null,
850: SystemAction.get(SaveAsTemplateAction.class),
851: SystemAction.get(FileSystemAction.class) };
852: return actions;
853: }
854:
855: Action[] myActions;
856:
857: }
858:
859: /** returns provider actions for specified DataObject or for Project if null.
860: *
861: * @param dataObject for which actions keysList should be provided.
862: * It can be DataObject for file,
863: * DataFolder for forlder
864: * or null for Project and SrcRoot Node.
865: *
866: * @returns Folder menu items for given DataFolder,
867: * File menu items for DataObject,
868: * Project menu items for null
869: */
870: private Action[] getProviderActions() {
871: Action[] actions = new Action[] {};
872: List<Action> list = new LinkedList<Action>();
873:
874: Command[] commands = getProviderCommands();
875: if (commands.length > 0) {
876: list.add(null);
877:
878: for (Command command : commands) {
879: if (command == null) {
880: list.add(null);
881: continue;
882: }
883: list.add(getWrapperAction(command));
884: }
885:
886: list.add(null);
887: actions = list.toArray(actions);
888: }
889:
890: return actions;
891: }
892:
893: private Command[] getProviderCommands() {
894: Command[] commands = null;
895:
896: WebServerProvider provider = Utils.getProvider(getProject());
897: if (provider != null) {
898: CommandProvider commandProvider = provider
899: .getCommandProvider();
900:
901: commands = commandProvider.getCommands(getProject());
902: }
903: if (commands == null) {
904: commands = new Command[] {};
905: }
906: return commands;
907: }
908:
909: private class PhpSourcesFilter implements DataFilter {
910:
911: private static final long serialVersionUID = -7439706583318056955L;
912:
913: /*
914: * (non-Javadoc)
915: *
916: * @see org.openide.loaders.DataFilter#acceptDataObject(org.openide.loaders.DataObject)
917: */
918: public boolean acceptDataObject(DataObject object) {
919: return isNotTemporaryFile(object)
920: && isNotProjectFile(object);
921: }
922:
923: private boolean isNotProjectFile(DataObject object) {
924: try {
925:
926: if (PROJECT_XML != null) {
927: File nbProject = PROJECT_XML.getParentFile()
928: .getCanonicalFile();
929: File f = FileUtil.toFile(object.getPrimaryFile())
930: .getCanonicalFile();
931: return nbProject != null && !nbProject.equals(f);
932: } else {
933: return true;
934: }
935: } catch (IOException e) {
936: return false;
937: }
938: }
939:
940: private boolean isNotTemporaryFile(DataObject object) {
941: String name = object.getPrimaryFile().getNameExt();
942: return !name.endsWith(PhpProject.TMP_FILE_POSTFIX);
943: }
944:
945: private final File PROJECT_XML = getProject().getHelper()
946: .resolveFile(AntProjectHelper.PROJECT_XML_PATH);
947: }
948:
949: private final Map<Command, Action> myActionsByCommand;
950:
951: private final PhpProject myProject;
952: private final SubprojectProvider mySubProjectProvider;
953: }
|