/*************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. This code is licensed under the Visual Studio SDK license terms. THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. ***************************************************************************/ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using Microsoft.VisualStudio; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using ErrorHandler = Microsoft.VisualStudio.ErrorHandler; using ShellConstants = Microsoft.VisualStudio.Shell.Interop.Constants; namespace Microsoft.VisualStudio.Project { [CLSCompliant(false), ComVisible(true)] public class NestedProjectNode : HierarchyNode, IPropertyNotifySink { #region fields private IVsHierarchy nestedHierarchy; Guid projectInstanceGuid = Guid.Empty; private string projectName = String.Empty; private string projectPath = String.Empty; private ImageHandler imageHandler; /// /// Defines an object that will be a mutex for this object for synchronizing thread calls. /// private static volatile object Mutex = new object(); /// /// Sets the dispose flag on the object. /// private bool isDisposed; // A cooike retrieved when advising on property chnanged events. private uint projectPropertyNotifySinkCookie; #endregion #region properties internal IVsHierarchy NestedHierarchy { get { return this.nestedHierarchy; } } #endregion #region virtual properties /// /// Returns the __VSADDVPFLAGS that will be passed in when calling AddVirtualProjectEx /// protected virtual uint VirtualProjectFlags { get { return 0; } } #endregion #region overridden properties /// /// The path of the nested project. /// public override string Url { get { return this.projectPath; } } /// /// The Caption of the nested project. /// public override string Caption { get { return Path.GetFileNameWithoutExtension(this.projectName); } } public override Guid ItemTypeGuid { get { return VSConstants.GUID_ItemType_SubProject; } } /// /// Defines whether a node can execute a command if in selection. /// We do this in order to let the nested project to handle the execution of its own commands. /// public override bool CanExecuteCommand { get { return false; } } public override int SortPriority { get { return DefaultSortOrderNode.NestedProjectNode; } } protected bool IsDisposed { get { return this.isDisposed; } set { this.isDisposed = value; } } #endregion #region ctor protected NestedProjectNode() { } public NestedProjectNode(ProjectNode root, ProjectElement element) : base(root, element) { this.IsExpanded = true; } #endregion #region IPropertyNotifySink Members /// /// Notifies a sink that the [bindable] property specified by dispID has changed. /// If dispID is DISPID_UNKNOWN, then multiple properties have changed together. /// The client (owner of the sink) should then retrieve the current value of each property of interest from the object that generated the notification. /// In our case we will care about the VSLangProj80.VsProjPropId.VBPROJPROPID_FileName and update the changes in the parent project file. /// /// Dispatch identifier of the property that is about to change or DISPID_UNKNOWN if multiple properties are about to change. public virtual void OnChanged(int dispid) { if (dispid == (int)VSLangProj80.VsProjPropId.VBPROJPROPID_FileName) { // Get the filename of the nested project. Inetead of asking the label on the nested we ask the filename, since the label might not yet been set. IVsProject3 nestedProject = this.nestedHierarchy as IVsProject3; if (nestedProject != null) { string document; ErrorHandler.ThrowOnFailure(nestedProject.GetMkDocument(VSConstants.VSITEMID_ROOT, out document)); this.RenameNestedProjectInParentProject(Path.GetFileNameWithoutExtension(document)); // We need to redraw the caption since for some reason, by intervining to the OnChanged event the Caption is not updated. this.ReDraw(UIHierarchyElement.Caption); } } } /// /// Notifies a sink that a [requestedit] property is about to change and that the object is asking the sink how to proceed. /// /// Dispatch identifier of the property that is about to change or DISPID_UNKNOWN if multiple properties are about to change. public virtual void OnRequestEdit(int dispid) { } #endregion #region public methods #endregion #region overridden methods /// /// Get the automation object for the NestedProjectNode /// /// An instance of the Automation.OANestedProjectItem type if succeded public override object GetAutomationObject() { //Validate that we are not disposed or the project is closing if (this.isDisposed || this.ProjectMgr == null || this.ProjectMgr.IsClosed) { return null; } return new Automation.OANestedProjectItem(this.ProjectMgr.GetAutomationObject() as Automation.OAProject, this); } /// /// Gets properties of a given node or of the hierarchy. /// /// Identifier of the hierarchy property /// It return an object which type is dependent on the propid. public override object GetProperty(int propId) { __VSHPROPID vshPropId = (__VSHPROPID)propId; switch (vshPropId) { default: return base.GetProperty(propId); case __VSHPROPID.VSHPROPID_Expandable: return true; case __VSHPROPID.VSHPROPID_BrowseObject: case __VSHPROPID.VSHPROPID_HandlesOwnReload: return this.DelegateGetPropertyToNested(propId); } } /// /// Gets properties whose values are GUIDs. /// /// Identifier of the hierarchy property /// Pointer to a GUID property specified in propid /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public override int GetGuidProperty(int propid, out Guid guid) { guid = Guid.Empty; switch ((__VSHPROPID)propid) { case __VSHPROPID.VSHPROPID_ProjectIDGuid: guid = this.projectInstanceGuid; break; default: return base.GetGuidProperty(propid, out guid); } CCITracing.TraceCall(String.Format(CultureInfo.CurrentCulture, "Guid for {0} property", propid)); if (guid.CompareTo(Guid.Empty) == 0) { return VSConstants.DISP_E_MEMBERNOTFOUND; } return VSConstants.S_OK; } /// /// Determines whether the hierarchy item changed. /// /// Item identifier of the hierarchy item contained in VSITEMID /// Pointer to the IUnknown interface of the hierarchy item. /// TRUE if the hierarchy item changed. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public override int IsItemDirty(uint itemId, IntPtr punkDocData, out int pfDirty) { Debug.Assert(this.nestedHierarchy != null, "The nested hierarchy object must be created before calling this method"); Debug.Assert(punkDocData != IntPtr.Zero, "docData intptr was zero"); // Get an IPersistFileFormat object from docData object IPersistFileFormat persistFileFormat = Marshal.GetTypedObjectForIUnknown(punkDocData, typeof(IPersistFileFormat)) as IPersistFileFormat; Debug.Assert(persistFileFormat != null, "The docData object does not implement the IPersistFileFormat interface"); // Call IsDirty on the IPersistFileFormat interface ErrorHandler.ThrowOnFailure(persistFileFormat.IsDirty(out pfDirty)); return VSConstants.S_OK; } /// /// Saves the hierarchy item to disk. /// /// Flags whose values are taken from the VSSAVEFLAGS enumeration. /// File name to be applied when dwSave is set to VSSAVE_SilentSave. /// Item identifier of the hierarchy item saved from VSITEMID. /// Pointer to the IUnknown interface of the hierarchy item saved. /// TRUE if the save action was canceled. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public override int SaveItem(VSSAVEFLAGS dwSave, string silentSaveAsName, uint itemid, IntPtr punkDocData, out int pfCancelled) { // Don't ignore/unignore file changes // Use Advise/Unadvise to work around rename situations try { this.StopObservingNestedProjectFile(); Debug.Assert(this.nestedHierarchy != null, "The nested hierarchy object must be created before calling this method"); Debug.Assert(punkDocData != IntPtr.Zero, "docData intptr was zero"); // Get an IPersistFileFormat object from docData object (we don't call release on punkDocData since did not increment its ref count) IPersistFileFormat persistFileFormat = Marshal.GetTypedObjectForIUnknown(punkDocData, typeof(IPersistFileFormat)) as IPersistFileFormat; Debug.Assert(persistFileFormat != null, "The docData object does not implement the IPersistFileFormat interface"); IVsUIShell uiShell = this.GetService(typeof(SVsUIShell)) as IVsUIShell; string newName; ErrorHandler.ThrowOnFailure(uiShell.SaveDocDataToFile(dwSave, persistFileFormat, silentSaveAsName, out newName, out pfCancelled)); // When supported do a rename of the nested project here } finally { // Succeeded or not we must hook to the file change events // Don't ignore/unignore file changes // Use Advise/Unadvise to work around rename situations this.ObserveNestedProjectFile(); } return VSConstants.S_OK; } /// /// Gets the icon handle. It tries first the nested to get the icon handle. If that is not supported it will get it from /// the image list of the nested if that is supported. If neither of these is supported a default image will be shown. /// /// An object representing the icon. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "Microsoft.VisualStudio.Shell.Interop.IVsHierarchy.GetProperty(System.UInt32,System.Int32,System.Object@)")] public override object GetIconHandle(bool open) { Debug.Assert(this.nestedHierarchy != null, "The nested hierarchy object must be created before calling this method"); object iconHandle = null; this.nestedHierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_IconHandle, out iconHandle); if (iconHandle == null) { if (null == imageHandler) { InitImageHandler(); } // Try to get an icon from the nested hierrachy image list. if (imageHandler.ImageList != null) { object imageIndexAsObject = null; if (this.nestedHierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_IconIndex, out imageIndexAsObject) == VSConstants.S_OK && imageIndexAsObject != null) { int imageIndex = (int)imageIndexAsObject; if (imageIndex < imageHandler.ImageList.Images.Count) { iconHandle = imageHandler.GetIconHandle(imageIndex); } } } if (null == iconHandle) { iconHandle = this.ProjectMgr.ImageHandler.GetIconHandle((int)ProjectNode.ImageName.Application); } } return iconHandle; } /// /// Return S_OK. Implementation of Closing a nested project is done in CloseNestedProject which is called by CloseChildren. /// /// S_OK public override int Close() { return VSConstants.S_OK; } /// /// Returns the moniker of the nested project. /// /// public override string GetMkDocument() { Debug.Assert(this.nestedHierarchy != null, "The nested hierarchy object must be created before calling this method"); if (this.isDisposed || this.ProjectMgr == null || this.ProjectMgr.IsClosed) { return String.Empty; } return this.projectPath; } /// /// Called by the shell when a node has been renamed from the GUI /// /// The name of the new label. /// A success or failure value. public override int SetEditLabel(string label) { int result = this.DelegateSetPropertyToNested((int)__VSHPROPID.VSHPROPID_EditLabel, label); if (ErrorHandler.Succeeded(result)) { this.RenameNestedProjectInParentProject(label); } return result; } /// /// Called by the shell to get the node caption when the user tries to rename from the GUI /// /// the node cation public override string GetEditLabel() { return (string)this.DelegateGetPropertyToNested((int)__VSHPROPID.VSHPROPID_EditLabel); } /// /// This is temporary until we have support for re-adding a nested item /// protected override bool CanDeleteItem(__VSDELETEITEMOPERATION deleteOperation) { return false; } /// /// Delegates the call to the inner hierarchy. /// /// Reserved parameter defined at the IVsPersistHierarchyItem2::ReloadItem parameter. protected internal override void ReloadItem(uint reserved) { #region precondition if (this.isDisposed || this.ProjectMgr == null || this.ProjectMgr.IsClosed) { throw new InvalidOperationException(); } Debug.Assert(this.nestedHierarchy != null, "The nested hierarchy object must be created before calling this method"); #endregion IVsPersistHierarchyItem2 persistHierachyItem = this.nestedHierarchy as IVsPersistHierarchyItem2; // We are expecting that if we get called then the nestedhierarchy supports IVsPersistHierarchyItem2, since then hierrachy should support handling its own reload. // There should be no errormessage to the user since this is an internal error, that it cannot be fixed at user level. if (persistHierachyItem == null) { throw new InvalidOperationException(); } ErrorHandler.ThrowOnFailure(persistHierachyItem.ReloadItem(VSConstants.VSITEMID_ROOT, reserved)); } /// /// Flag indicating that changes to a file can be ignored when item is saved or reloaded. /// /// Flag indicating whether or not to ignore changes (1 to ignore, 0 to stop ignoring). protected internal override void IgnoreItemFileChanges(bool ignoreFlag) { #region precondition if (this.isDisposed || this.ProjectMgr == null || this.ProjectMgr.IsClosed) { throw new InvalidOperationException(); } Debug.Assert(this.nestedHierarchy != null, "The nested hierarchy object must be created before calling this method"); #endregion this.IgnoreNestedProjectFile(ignoreFlag); IVsPersistHierarchyItem2 persistHierachyItem = this.nestedHierarchy as IVsPersistHierarchyItem2; // If the IVsPersistHierarchyItem2 is not implemented by the nested just return if (persistHierachyItem == null) { return; } ErrorHandler.ThrowOnFailure(persistHierachyItem.IgnoreItemFileChanges(VSConstants.VSITEMID_ROOT, ignoreFlag ? 1 : 0)); } /// /// Sets the VSADDFILEFLAGS that will be used to call the IVsTrackProjectDocumentsEvents2 OnAddFiles /// /// The files to which an array of VSADDFILEFLAGS has to be specified. /// protected internal override VSADDFILEFLAGS[] GetAddFileFlags(string[] files) { if (files == null || files.Length == 0) { return new VSADDFILEFLAGS[1] { VSADDFILEFLAGS.VSADDFILEFLAGS_NoFlags }; } VSADDFILEFLAGS[] addFileFlags = new VSADDFILEFLAGS[files.Length]; for (int i = 0; i < files.Length; i++) { addFileFlags[i] = VSADDFILEFLAGS.VSADDFILEFLAGS_IsNestedProjectFile; } return addFileFlags; } /// /// Sets the VSQUERYADDFILEFLAGS that will be used to call the IVsTrackProjectDocumentsEvents2 OnQueryAddFiles /// /// The files to which an array of VSADDFILEFLAGS has to be specified. /// protected internal override VSQUERYADDFILEFLAGS[] GetQueryAddFileFlags(string[] files) { if (files == null || files.Length == 0) { return new VSQUERYADDFILEFLAGS[1] { VSQUERYADDFILEFLAGS.VSQUERYADDFILEFLAGS_NoFlags }; } VSQUERYADDFILEFLAGS[] queryAddFileFlags = new VSQUERYADDFILEFLAGS[files.Length]; for (int i = 0; i < files.Length; i++) { queryAddFileFlags[i] = VSQUERYADDFILEFLAGS.VSQUERYADDFILEFLAGS_IsNestedProjectFile; } return queryAddFileFlags; } /// /// Sets the VSREMOVEFILEFLAGS that will be used to call the IVsTrackProjectDocumentsEvents2 OnRemoveFiles /// /// The files to which an array of VSREMOVEFILEFLAGS has to be specified. /// protected internal override VSREMOVEFILEFLAGS[] GetRemoveFileFlags(string[] files) { if (files == null || files.Length == 0) { return new VSREMOVEFILEFLAGS[1] { VSREMOVEFILEFLAGS.VSREMOVEFILEFLAGS_NoFlags }; } VSREMOVEFILEFLAGS[] removeFileFlags = new VSREMOVEFILEFLAGS[files.Length]; for (int i = 0; i < files.Length; i++) { removeFileFlags[i] = VSREMOVEFILEFLAGS.VSREMOVEFILEFLAGS_IsNestedProjectFile; } return removeFileFlags; } /// /// Sets the VSQUERYREMOVEFILEFLAGS that will be used to call the IVsTrackProjectDocumentsEvents2 OnQueryRemoveFiles /// /// The files to which an array of VSQUERYREMOVEFILEFLAGS has to be specified. /// protected internal override VSQUERYREMOVEFILEFLAGS[] GetQueryRemoveFileFlags(string[] files) { if (files == null || files.Length == 0) { return new VSQUERYREMOVEFILEFLAGS[1] { VSQUERYREMOVEFILEFLAGS.VSQUERYREMOVEFILEFLAGS_NoFlags }; } VSQUERYREMOVEFILEFLAGS[] queryRemoveFileFlags = new VSQUERYREMOVEFILEFLAGS[files.Length]; for (int i = 0; i < files.Length; i++) { queryRemoveFileFlags[i] = VSQUERYREMOVEFILEFLAGS.VSQUERYREMOVEFILEFLAGS_IsNestedProjectFile; } return queryRemoveFileFlags; } #endregion #region virtual methods /// /// Initialize the nested hierarhy node. /// /// The file name of the nested project. /// The location of the nested project. /// The name of the project. /// The nested project creation flags /// This methos should be called just after a NestedProjectNode object is created. public virtual void Init(string fileName, string destination, string projectName, __VSCREATEPROJFLAGS createFlags) { if (String.IsNullOrEmpty(fileName)) { throw new ArgumentException(SR.GetString(SR.ParameterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "fileName"); } if (String.IsNullOrEmpty(destination)) { throw new ArgumentException(SR.GetString(SR.ParameterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "destination"); } this.projectName = Path.GetFileName(fileName); this.projectPath = Path.Combine(destination, this.projectName); // get the IVsSolution interface from the global service provider IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution; Debug.Assert(solution != null, "Could not get the IVsSolution object from the services exposed by this project"); if (solution == null) { throw new InvalidOperationException(); } // Get the project type guid from project element string typeGuidString = this.ItemNode.GetMetadataAndThrow(ProjectFileConstants.TypeGuid, new InvalidOperationException()); Guid projectFactoryGuid = Guid.Empty; if (!String.IsNullOrEmpty(typeGuidString)) { projectFactoryGuid = new Guid(typeGuidString); } // Get the project factory. IVsProjectFactory projectFactory; ErrorHandler.ThrowOnFailure(solution.GetProjectFactory((uint)0, new Guid[] { projectFactoryGuid }, fileName, out projectFactory)); this.CreateProjectDirectory(); //Create new project using factory int cancelled; Guid refiid = NativeMethods.IID_IUnknown; IntPtr projectPtr = IntPtr.Zero; try { ErrorHandler.ThrowOnFailure(projectFactory.CreateProject(fileName, destination, projectName, (uint)createFlags, ref refiid, out projectPtr, out cancelled)); if (projectPtr != IntPtr.Zero) { this.nestedHierarchy = Marshal.GetTypedObjectForIUnknown(projectPtr, typeof(IVsHierarchy)) as IVsHierarchy; Debug.Assert(this.nestedHierarchy != null, "Nested hierarchy could not be created"); Debug.Assert(cancelled == 0); } } finally { if (projectPtr != IntPtr.Zero) { // We created a new instance of the project, we need to call release to decrement the ref count // the RCW (this.nestedHierarchy) still has a reference to it which will keep it alive Marshal.Release(projectPtr); } } if (cancelled != 0 && this.nestedHierarchy == null) { ErrorHandler.ThrowOnFailure(VSConstants.OLE_E_PROMPTSAVECANCELLED); } // Link into the nested VS hierarchy. ErrorHandler.ThrowOnFailure(this.nestedHierarchy.SetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ParentHierarchy, this.ProjectMgr)); ErrorHandler.ThrowOnFailure(this.nestedHierarchy.SetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ParentHierarchyItemid, (object)(int)this.ID)); this.LockRDTEntry(); this.ConnectPropertyNotifySink(); } /// /// Links a nested project as a virtual project to the solution. /// protected internal virtual void AddVirtualProject() { // This is the second step in creating and adding a nested project. The inner hierarchy must have been // already initialized at this point. #region precondition if (this.nestedHierarchy == null) { throw new InvalidOperationException(); } #endregion // get the IVsSolution interface from the global service provider IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution; Debug.Assert(solution != null, "Could not get the IVsSolution object from the services exposed by this project"); if (solution == null) { throw new InvalidOperationException(); } this.InitializeInstanceGuid(); // Add virtual project to solution. ErrorHandler.ThrowOnFailure(solution.AddVirtualProjectEx(this.nestedHierarchy, this.VirtualProjectFlags, ref this.projectInstanceGuid)); // Now set up to listen on file changes on the nested project node. this.ObserveNestedProjectFile(); } /// /// The method that does the cleanup. /// /// protected override void Dispose(bool disposing) { // Everybody can go here. if (!this.isDisposed) { try { // Synchronize calls to the Dispose simulteniously. lock (Mutex) { if (disposing) { this.DisconnectPropertyNotifySink(); this.StopObservingNestedProjectFile(); // If a project cannot load it may happen that the imagehandler is not instantiated. if (this.imageHandler != null) { this.imageHandler.Close(); } } } } finally { base.Dispose(disposing); this.isDisposed = true; } } } /// /// Creates the project directory if it does not exist. /// /// protected virtual void CreateProjectDirectory() { string directoryName = Path.GetDirectoryName(this.projectPath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } } /// /// Lock the RDT Entry for the nested project. /// By default this document is marked as "Dont Save as". That means the menu File->SaveAs is disabled for the /// nested project node. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "RDT")] protected virtual void LockRDTEntry() { // Define flags for the nested project document _VSRDTFLAGS flags = _VSRDTFLAGS.RDT_VirtualDocument | _VSRDTFLAGS.RDT_ProjSlnDocument; ; // Request the RDT service IVsRunningDocumentTable rdt = this.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable; Debug.Assert(rdt != null, " Could not get running document table from the services exposed by this project"); if (rdt == null) { throw new InvalidOperationException(); } // First we see if someone else has opened the requested view of the file. uint itemid; IntPtr docData = IntPtr.Zero; IVsHierarchy ivsHierarchy; uint docCookie; IntPtr projectPtr = IntPtr.Zero; try { ErrorHandler.ThrowOnFailure(rdt.FindAndLockDocument((uint)flags, this.projectPath, out ivsHierarchy, out itemid, out docData, out docCookie)); flags |= _VSRDTFLAGS.RDT_EditLock; if (ivsHierarchy != null && docCookie != (uint)ShellConstants.VSDOCCOOKIE_NIL) { if (docCookie != this.DocCookie) { this.DocCookie = docCookie; } } else { // get inptr for hierarchy projectPtr = Marshal.GetIUnknownForObject(this.nestedHierarchy); Debug.Assert(projectPtr != IntPtr.Zero, " Project pointer for the nested hierarchy has not been initialized"); ErrorHandler.ThrowOnFailure(rdt.RegisterAndLockDocument((uint)flags, this.projectPath, this.ProjectMgr, this.ID, projectPtr, out docCookie)); this.DocCookie = docCookie; Debug.Assert(this.DocCookie != (uint)ShellConstants.VSDOCCOOKIE_NIL, "Invalid cookie when registering document in the running document table."); //we must also set the doc cookie on the nested hier this.SetDocCookieOnNestedHier(this.DocCookie); } } finally { // Release all Inptr's that that were given as out pointers if (docData != IntPtr.Zero) { Marshal.Release(docData); } if (projectPtr != IntPtr.Zero) { Marshal.Release(projectPtr); } } } /// /// Unlock the RDT entry for the nested project /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "RDT")] protected virtual void UnlockRDTEntry() { if (this.isDisposed || this.ProjectMgr == null || this.ProjectMgr.IsClosed) { return; } // First we see if someone else has opened the requested view of the file. IVsRunningDocumentTable rdt = this.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable; if (rdt != null && this.DocCookie != (int)ShellConstants.VSDOCCOOKIE_NIL) { _VSRDTFLAGS flags = _VSRDTFLAGS.RDT_EditLock; ErrorHandler.ThrowOnFailure(rdt.UnlockDocument((uint)flags, (uint)this.DocCookie)); } this.DocCookie = (int)ShellConstants.VSDOCCOOKIE_NIL; } /// /// Renames the project file in the parent project structure. /// /// The new label. protected virtual void RenameNestedProjectInParentProject(string label) { string existingLabel = this.Caption; if (String.Compare(existingLabel, label, StringComparison.Ordinal) == 0) { return; } string oldFileName = this.projectPath; string oldPath = this.Url; try { this.StopObservingNestedProjectFile(); this.ProjectMgr.SuspendMSBuild(); // Check out the project file if necessary. if (!this.ProjectMgr.QueryEditProjectFile(false)) { throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED); } string newFileName = label + Path.GetExtension(oldFileName); this.SaveNestedProjectItemInProjectFile(newFileName); string projectDirectory = Path.GetDirectoryName(oldFileName); // update state. this.projectName = newFileName; this.projectPath = Path.Combine(projectDirectory, this.projectName); // Unload and lock the RDT entries this.UnlockRDTEntry(); this.LockRDTEntry(); // Since actually this is a rename in our hierarchy notify the tracker that a rename has happened. this.ProjectMgr.Tracker.OnItemRenamed(oldPath, this.projectPath, VSRENAMEFILEFLAGS.VSRENAMEFILEFLAGS_IsNestedProjectFile); } finally { this.ObserveNestedProjectFile(); this.ProjectMgr.ResumeMSBuild(this.ProjectMgr.ReEvaluateProjectFileTargetName); } } /// /// Saves the nested project information in the project file. /// /// protected virtual void SaveNestedProjectItemInProjectFile(string newFileName) { string existingInclude = this.ItemNode.Item.EvaluatedInclude; string existingRelativePath = Path.GetDirectoryName(existingInclude); string newRelativePath = Path.Combine(existingRelativePath, newFileName); this.ItemNode.Rename(newRelativePath); } #endregion #region helper methods /// /// Closes a nested project and releases the nested hierrachy pointer. /// internal void CloseNestedProjectNode() { if (this.isDisposed || this.ProjectMgr == null || this.ProjectMgr.IsClosed) { return; } uint itemid = VSConstants.VSITEMID_NIL; try { this.DisconnectPropertyNotifySink(); IVsUIHierarchy hier; IVsWindowFrame windowFrame; VsShellUtilities.IsDocumentOpen(this.ProjectMgr.Site, this.projectPath, Guid.Empty, out hier, out itemid, out windowFrame); if (itemid == VSConstants.VSITEMID_NIL) { this.UnlockRDTEntry(); } IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution; if (solution == null) { throw new InvalidOperationException(); } ErrorHandler.ThrowOnFailure(solution.RemoveVirtualProject(this.nestedHierarchy, 0)); } finally { this.StopObservingNestedProjectFile(); // if we haven't already release the RDT cookie, do so now. if (itemid == VSConstants.VSITEMID_NIL) { this.UnlockRDTEntry(); } this.Dispose(true); } } private void InitializeInstanceGuid() { if (this.projectInstanceGuid != Guid.Empty) { return; } Guid instanceGuid = Guid.Empty; Debug.Assert(this.nestedHierarchy != null, "The nested hierarchy object must be created before calling this method"); // This method should be called from the open children method, then we can safely use the IsNewProject property if (this.ProjectMgr.IsNewProject) { instanceGuid = Guid.NewGuid(); this.ItemNode.SetMetadata(ProjectFileConstants.InstanceGuid, instanceGuid.ToString("B")); ErrorHandler.ThrowOnFailure(this.nestedHierarchy.SetGuidProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ProjectIDGuid, ref instanceGuid)); } else { // Get a guid from the nested hiererachy. Guid nestedHiererachyInstanceGuid; ErrorHandler.ThrowOnFailure(this.nestedHierarchy.GetGuidProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ProjectIDGuid, out nestedHiererachyInstanceGuid)); // Get instance guid from the project file. If it does not exist then we create one. string instanceGuidAsString = this.ItemNode.GetMetadata(ProjectFileConstants.InstanceGuid); // 1. nestedHiererachyInstanceGuid is empty and instanceGuidAsString is empty then create a new one. // 2. nestedHiererachyInstanceGuid is empty and instanceGuidAsString not empty use instanceGuidAsString and update the nested project object by calling SetGuidProperty. // 3. nestedHiererachyInstanceGuid is not empty instanceGuidAsString is empty then use nestedHiererachyInstanceGuid and update the outer project element. // 4. nestedHiererachyInstanceGuid is not empty instanceGuidAsString is empty then use nestedHiererachyInstanceGuid and update the outer project element. if (nestedHiererachyInstanceGuid == Guid.Empty && String.IsNullOrEmpty(instanceGuidAsString)) { instanceGuid = Guid.NewGuid(); } else if (nestedHiererachyInstanceGuid == Guid.Empty && !String.IsNullOrEmpty(instanceGuidAsString)) { instanceGuid = new Guid(instanceGuidAsString); ErrorHandler.ThrowOnFailure(this.nestedHierarchy.SetGuidProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ProjectIDGuid, ref instanceGuid)); } else if (nestedHiererachyInstanceGuid != Guid.Empty) { instanceGuid = nestedHiererachyInstanceGuid; // If the instanceGuidAsString is empty then creating a guid out of it would throw an exception. if (String.IsNullOrEmpty(instanceGuidAsString) || nestedHiererachyInstanceGuid != new Guid(instanceGuidAsString)) { this.ItemNode.SetMetadata(ProjectFileConstants.InstanceGuid, instanceGuid.ToString("B")); } } } this.projectInstanceGuid = instanceGuid; } private void SetDocCookieOnNestedHier(uint itemDocCookie) { object docCookie = (int)itemDocCookie; try { ErrorHandler.ThrowOnFailure(this.nestedHierarchy.SetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ItemDocCookie, docCookie)); } catch (NotImplementedException) { //we swallow this exception on purpose } } private void InitImageHandler() { Debug.Assert(this.nestedHierarchy != null, "The nested hierarchy object must be created before calling this method"); if (null == imageHandler) { imageHandler = new ImageHandler(); } object imageListAsPointer = null; ErrorHandler.ThrowOnFailure(this.nestedHierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_IconImgList, out imageListAsPointer)); if (imageListAsPointer != null) { this.imageHandler.ImageList = Utilities.GetImageList(imageListAsPointer); } } /// /// Delegates Getproperty calls to the inner nested. /// /// The property to delegate. /// The return of the GetProperty from nested. private object DelegateGetPropertyToNested(int propID) { if (!this.ProjectMgr.IsClosed) { Debug.Assert(this.nestedHierarchy != null, "The nested hierarchy object must be created before calling this method"); object returnValue; // Do not throw since some project types will return E_FAIL if they do not support a property. int result = this.nestedHierarchy.GetProperty(VSConstants.VSITEMID_ROOT, propID, out returnValue); if (ErrorHandler.Succeeded(result)) { return returnValue; } } return null; } /// /// Delegates Setproperty calls to the inner nested. /// /// The property to delegate. /// The property to set. /// The return of the SetProperty from nested. private int DelegateSetPropertyToNested(int propID, object value) { if (this.ProjectMgr.IsClosed) { return VSConstants.E_FAIL; } Debug.Assert(this.nestedHierarchy != null, "The nested hierarchy object must be created before calling this method"); // Do not throw since some project types will return E_FAIL if they do not support a property. return this.nestedHierarchy.SetProperty(VSConstants.VSITEMID_ROOT, propID, value); } /// /// Starts observing changes on this file. /// private void ObserveNestedProjectFile() { ProjectContainerNode parent = this.ProjectMgr as ProjectContainerNode; Debug.Assert(parent != null, "The parent project for nested projects should be subclassed from ProjectContainerNode"); parent.NestedProjectNodeReloader.ObserveItem(this.GetMkDocument(), this.ID); } /// /// Stops observing changes on this file. /// private void StopObservingNestedProjectFile() { ProjectContainerNode parent = this.ProjectMgr as ProjectContainerNode; Debug.Assert(parent != null, "The parent project for nested projects should be subclassed from ProjectContainerNode"); parent.NestedProjectNodeReloader.StopObservingItem(this.GetMkDocument()); } /// /// Ignores observing changes on this file depending on the boolean flag. /// /// Flag indicating whether or not to ignore changes (1 to ignore, 0 to stop ignoring). private void IgnoreNestedProjectFile(bool ignoreFlag) { ProjectContainerNode parent = this.ProjectMgr as ProjectContainerNode; Debug.Assert(parent != null, "The parent project for nested projects should be subclassed from ProjectContainerNode"); parent.NestedProjectNodeReloader.IgnoreItemChanges(this.GetMkDocument(), ignoreFlag); } /// /// We need to advise property notify sink on project properties so that /// we know when the project file is renamed through a property. /// private void ConnectPropertyNotifySink() { if (this.projectPropertyNotifySinkCookie != (uint)ShellConstants.VSCOOKIE_NIL) { return; } IConnectionPoint connectionPoint = this.GetConnectionPointFromPropertySink(); if (connectionPoint != null) { connectionPoint.Advise(this, out this.projectPropertyNotifySinkCookie); } } /// /// Disconnects the propertynotify sink /// private void DisconnectPropertyNotifySink() { if (this.projectPropertyNotifySinkCookie == (uint)ShellConstants.VSCOOKIE_NIL) { return; } IConnectionPoint connectionPoint = this.GetConnectionPointFromPropertySink(); if (connectionPoint != null) { connectionPoint.Unadvise(this.projectPropertyNotifySinkCookie); this.projectPropertyNotifySinkCookie = (uint)ShellConstants.VSCOOKIE_NIL; } } /// /// Gets a ConnectionPoint for the IPropertyNotifySink interface. /// /// private IConnectionPoint GetConnectionPointFromPropertySink() { IConnectionPoint connectionPoint = null; object browseObject = this.GetProperty((int)__VSHPROPID.VSHPROPID_BrowseObject); IConnectionPointContainer connectionPointContainer = browseObject as IConnectionPointContainer; if (connectionPointContainer != null) { Guid guid = typeof(IPropertyNotifySink).GUID; connectionPointContainer.FindConnectionPoint(ref guid, out connectionPoint); } return connectionPoint; } #endregion } }