/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Apache License, Version 2.0. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Apache License, Version 2.0, please send an email to * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Apache License, Version 2.0. * * You must not remove this notice, or any other, from this software. * * ***************************************************************************/ using System; using System.Collections; using System.Collections.Generic; 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; //#define CCI_TRACING using Microsoft.VisualStudio.Shell.Interop; using OleConstants = Microsoft.VisualStudio.OLE.Interop.Constants; using VsCommands = Microsoft.VisualStudio.VSConstants.VSStd97CmdID; using VsCommands2K = Microsoft.VisualStudio.VSConstants.VSStd2KCmdID; using System.Windows.Forms; using Microsoft.Win32; using System.Reflection; namespace Microsoft.VisualStudio.Project { /// /// An object that deals with user interaction via a GUI in the form a hierarchy: a parent node with zero or more child nodes, each of which /// can itself be a hierarchy. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public abstract class HierarchyNode : IDisposable { public static readonly Guid SolutionExplorer = new Guid(EnvDTE.Constants.vsWindowKindSolutionExplorer); public const int NoImage = -1; #if DEBUG public static int LastTracedProperty; #endif private ProjectElement itemNode; private ProjectNode projectMgr; private HierarchyNode parentNode; private HierarchyNode nextSibling; private HierarchyNode firstChild; private uint hierarchyId; private HierarchyNodeFlags flags; private NodeProperties nodeProperties; private OleServiceProvider oleServiceProvider = new OleServiceProvider(); /// /// Has the object been disposed. /// /// We will not specify a property for isDisposed, rather it is expected that the a private flag is defined /// on all subclasses. We do not want get in a situation where the base class's dipose is not called because a child sets the flag through the property. private bool isDisposed; #region abstract properties /// /// The URL of the node. /// /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] public abstract string Url { get; } /// /// The Caption of the node. /// /// public abstract string Caption { get; } /// /// The item type guid associated to a node. /// /// public abstract Guid ItemTypeGuid { get; } #endregion #region virtual properties public virtual bool IsNonMemberItem { get { return false; } } /// /// Gets the full path to where children of this node live on disk. /// /// This should only be called on nodes which actually can have children, such /// as folders and project nodes. For all other nodes this will raise an /// InvalidOperationException. /// /// For a project node, this returns the project home folder. For folder /// nodes this returns the folder's path. /// public virtual string FullPathToChildren { get { Debug.Fail("This node cannot have children"); throw new InvalidOperationException(); } } /// /// Defines a string that is used to separate the name relation from the extension /// public virtual string NameRelationSeparator { get { return "."; } } public virtual int MenuCommandId { get { return VsMenus.IDM_VS_CTXT_NOCOMMANDS; } } public virtual Guid MenuCommandGuid { get { return VsMenus.guidSHLMainMenu; } } /// /// Return an imageindex /// /// public virtual int ImageIndex { get { return NoImage; } } /// /// Return an state icon index /// /// /// /// Sets the state icon for a file. /// public virtual VsStateIcon StateIconIndex { get { if (!this.ExcludeNodeFromScc) { IVsSccManager2 sccManager = this.ProjectMgr.Site.GetService(typeof(SVsSccManager)) as IVsSccManager2; if (sccManager != null) { VsStateIcon[] statIcons = new VsStateIcon[1] { VsStateIcon.STATEICON_NOSTATEICON }; uint[] sccStatus = new uint[1] { 0 }; // Get the glyph from the scc manager. Note that it will fail in command line // scenarios. if (ErrorHandler.Succeeded(sccManager.GetSccGlyph(1, new string[] { this.GetMkDocument() }, statIcons, sccStatus))) { return statIcons[0]; } } } return VsStateIcon.STATEICON_NOSTATEICON; } } public virtual bool IsLinkFile { get { return false; } } protected virtual VSOVERLAYICON OverlayIconIndex { get { return VSOVERLAYICON.OVERLAYICON_NONE; } } /// /// Defines whether a node can execute a command if in selection. /// public virtual bool CanExecuteCommand { get { return true; } } /// /// Used to determine the sort order of different node types /// in the solution explorer window. /// Nodes with the same priorities are sorted based on their captions. /// public virtual int SortPriority { get { return DefaultSortOrderNode.HierarchyNode; } } /// /// Returns an object that is a special view over this object; this is the value /// returned by the Object property of the automation objects. /// public virtual object Object { get { return this; } } #endregion #region properties /// /// Defines the properties attached to this node. /// public NodeProperties NodeProperties { get { if (null == nodeProperties) { nodeProperties = CreatePropertiesObject(); } return this.nodeProperties; } } public OleServiceProvider OleServiceProvider { get { return this.oleServiceProvider; } } [System.ComponentModel.BrowsableAttribute(false)] public ProjectNode ProjectMgr { get { return this.projectMgr; } set { this.projectMgr = value; } } [System.ComponentModel.BrowsableAttribute(false)] public HierarchyNode NextSibling { get { return this.nextSibling; } set { this.nextSibling = value; } } public HierarchyNode FirstChild { get { return this.firstChild; } set { this.firstChild = value; } } /// /// Returns a sequence containing all of this node's children. /// public IEnumerable AllChildren { get { for (HierarchyNode node = this.firstChild; node != null; node = node.nextSibling) { yield return node; } } } /// /// Gets or sets whether the node is currently visible in the hierarchy. /// /// Enables subsetting or supersetting the hierarchy view. /// public bool IsVisible { get { return flags.HasFlag(HierarchyNodeFlags.IsVisible); } set { if (value) { flags |= HierarchyNodeFlags.IsVisible; } else { flags &= ~HierarchyNodeFlags.IsVisible; } } } public HierarchyNode PreviousVisibleSibling { get { var prev = PreviousSibling; while (prev != null && !prev.IsVisible) { prev = prev.PreviousSibling; } return prev; } } public HierarchyNode NextVisibleSibling { get { var next = nextSibling; while (next != null && !next.IsVisible) { next = next.NextSibling; } return next; } } public HierarchyNode FirstVisibleChild { get { var next = FirstChild; while (next != null && !next.IsVisible) { next = next.NextSibling; } return next; } } [System.ComponentModel.BrowsableAttribute(false)] public HierarchyNode Parent { get { return this.parentNode; } set { this.parentNode = value; } } [System.ComponentModel.BrowsableAttribute(false)] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")] public uint ID { get { return this.hierarchyId; } set { this.hierarchyId = value; } } [System.ComponentModel.BrowsableAttribute(false)] public ProjectElement ItemNode { get { return itemNode; } set { itemNode = value; } } protected string GetAbsoluteUrlFromMsbuild() { string path = this.ItemNode.GetMetadata(ProjectFileConstants.Include); if (String.IsNullOrEmpty(path)) { return String.Empty; } return CommonUtils.GetAbsoluteFilePath(this.ProjectMgr.ProjectHome, path); } [System.ComponentModel.BrowsableAttribute(false)] public bool IsExpanded { get { return flags.HasFlag(HierarchyNodeFlags.IsExpanded); } set { if (value) { flags |= HierarchyNodeFlags.IsExpanded; } else { flags &= ~HierarchyNodeFlags.IsExpanded; } } } public HierarchyNode PreviousSibling { get { if (this.parentNode == null) return null; HierarchyNode prev = null; for (HierarchyNode child = this.parentNode.firstChild; child != null; child = child.nextSibling) { if (child == this) break; prev = child; } return prev; } } /// /// Specifies if a Node is under source control. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Scc")] public bool ExcludeNodeFromScc { get { return flags.HasFlag(HierarchyNodeFlags.ExcludeFromScc); } set { if (value) { flags |= HierarchyNodeFlags.ExcludeFromScc; } else { flags &= ~HierarchyNodeFlags.ExcludeFromScc; } } } /// /// Defines if a node a name relation to its parent node /// /// public bool HasParentNodeNameRelation { get { return flags.HasFlag(HierarchyNodeFlags.HasParentNodeNameRelation); } set { if (value) { flags |= HierarchyNodeFlags.HasParentNodeNameRelation; } else { flags &= HierarchyNodeFlags.HasParentNodeNameRelation; } } } #endregion #region ctors protected HierarchyNode() { IsExpanded = true; IsVisible = true; } protected HierarchyNode(ProjectNode root, ProjectElement element) { VsUtilities.ArgumentNotNull("root", root); this.projectMgr = root; this.itemNode = element; this.hierarchyId = this.projectMgr.ItemIdMap.Add(this); this.OleServiceProvider.AddService(typeof(IVsHierarchy), root, false); IsVisible = true; } /// /// Overloaded ctor. /// /// protected HierarchyNode(ProjectNode root) { VsUtilities.ArgumentNotNull("root", root); this.projectMgr = root; this.itemNode = new VirtualProjectElement(this.projectMgr); this.hierarchyId = this.projectMgr.ItemIdMap.Add(this); this.OleServiceProvider.AddService(typeof(IVsHierarchy), root, false); IsVisible = true; } #endregion #region virtual methods /// /// Creates an object derived from NodeProperties that will be used to expose properties /// spacific for this object to the property browser. /// /// protected virtual NodeProperties CreatePropertiesObject() { return null; } /// /// Return an iconhandle /// /// /// public virtual object GetIconHandle(bool open) { return null; } /// /// Removes a node from the hierarchy. /// /// The node to remove. public virtual void RemoveChild(HierarchyNode node) { VsUtilities.ArgumentNotNull("node", node); this.projectMgr.ItemIdMap.Remove(node); HierarchyNode last = null; for (HierarchyNode n = this.firstChild; n != null; n = n.nextSibling) { if (n == node) { if (last != null) { last.nextSibling = n.nextSibling; } if (n == this.firstChild) { this.firstChild = n.nextSibling; } return; } last = n; } throw new InvalidOperationException("Node not found"); } /// /// Returns an automation object representing this node /// /// The automation object public virtual object GetAutomationObject() { return new Automation.OAProjectItem(this.projectMgr.GetAutomationObject() as Automation.OAProject, this); } /// /// Returns a property object based on a property id /// /// the property id of the property requested /// the property object requested [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public virtual object GetProperty(int propId) { object result = null; switch ((__VSHPROPID)propId) { case __VSHPROPID.VSHPROPID_Expandable: result = (this.firstChild != null); break; case __VSHPROPID.VSHPROPID_Caption: result = this.Caption; break; case __VSHPROPID.VSHPROPID_Name: result = this.Caption; break; case __VSHPROPID.VSHPROPID_ExpandByDefault: result = false; break; case __VSHPROPID.VSHPROPID_IconImgList: result = this.ProjectMgr.ImageHandler.ImageList.Handle; break; case __VSHPROPID.VSHPROPID_OpenFolderIconIndex: case __VSHPROPID.VSHPROPID_IconIndex: int index = this.ImageIndex; if (index != NoImage) { result = index; } break; case __VSHPROPID.VSHPROPID_StateIconIndex: result = (int)this.StateIconIndex; break; case __VSHPROPID.VSHPROPID_OverlayIconIndex: result = (int)this.OverlayIconIndex; break; case __VSHPROPID.VSHPROPID_IconHandle: result = GetIconHandle(false); break; case __VSHPROPID.VSHPROPID_OpenFolderIconHandle: result = GetIconHandle(true); break; case __VSHPROPID.VSHPROPID_NextVisibleSibling: var nextVisible = NextVisibleSibling; result = (int)((nextVisible != null) ? nextVisible.ID : VSConstants.VSITEMID_NIL); break; case __VSHPROPID.VSHPROPID_NextSibling: result = (int)((this.nextSibling != null) ? this.nextSibling.hierarchyId : VSConstants.VSITEMID_NIL); break; case __VSHPROPID.VSHPROPID_IsNonMemberItem: result = IsNonMemberItem; break; case __VSHPROPID.VSHPROPID_IsHiddenItem: case __VSHPROPID.VSHPROPID_IsNonSearchable: result = !IsVisible; break; case __VSHPROPID.VSHPROPID_FirstChild: result = (int)((this.firstChild != null) ? this.firstChild.hierarchyId : VSConstants.VSITEMID_NIL); break; case __VSHPROPID.VSHPROPID_FirstVisibleChild: var firstVisible = FirstVisibleChild; result = (int)((firstVisible != null) ? firstVisible.hierarchyId : VSConstants.VSITEMID_NIL); break; case __VSHPROPID.VSHPROPID_Parent: if (null == this.parentNode) { unchecked { result = new IntPtr((int)VSConstants.VSITEMID_NIL); } } else { result = new IntPtr((int)this.parentNode.hierarchyId); // see bug 176470 } break; case __VSHPROPID.VSHPROPID_Root: result = Marshal.GetIUnknownForObject(this.projectMgr); break; case __VSHPROPID.VSHPROPID_Expanded: result = this.IsExpanded; break; case __VSHPROPID.VSHPROPID_BrowseObject: result = this.NodeProperties; if (result != null) result = new DispatchWrapper(result); break; case __VSHPROPID.VSHPROPID_EditLabel: if (this.ProjectMgr != null && !this.ProjectMgr.IsClosed && !this.ProjectMgr.IsCurrentStateASuppressCommandsMode()) { result = GetEditLabel(); } break; case __VSHPROPID.VSHPROPID_SaveName: //SaveName is the name shown in the Save and the Save Changes dialog boxes. result = this.Caption; break; case __VSHPROPID.VSHPROPID_ExtObject: result = GetAutomationObject(); break; } __VSHPROPID2 id2 = (__VSHPROPID2)propId; switch (id2) { case __VSHPROPID2.VSHPROPID_IsLinkFile: result = IsLinkFile; break; case __VSHPROPID2.VSHPROPID_NoDefaultNestedHierSorting: return true; // We are doing the sorting ourselves through VSHPROPID_FirstChild and VSHPROPID_NextSibling case __VSHPROPID2.VSHPROPID_CfgBrowseObjectCATID: case __VSHPROPID2.VSHPROPID_BrowseObjectCATID: { // If there is a browse object and it is a NodeProperties, then get it's CATID object browseObject = this.GetProperty((int)__VSHPROPID.VSHPROPID_BrowseObject); if (browseObject != null) { if (browseObject is DispatchWrapper) browseObject = ((DispatchWrapper)browseObject).WrappedObject; result = this.ProjectMgr.GetCATIDForType(browseObject.GetType()).ToString("B"); if (String.Equals(result as string, Guid.Empty.ToString("B"), StringComparison.Ordinal)) result = null; } break; } case __VSHPROPID2.VSHPROPID_ExtObjectCATID: { // If there is a extensibility object and it is a NodeProperties, then get it's CATID object extObject = this.GetProperty((int)__VSHPROPID.VSHPROPID_ExtObject); if (extObject != null) { if (extObject is DispatchWrapper) extObject = ((DispatchWrapper)extObject).WrappedObject; result = this.ProjectMgr.GetCATIDForType(extObject.GetType()).ToString("B"); if (String.Equals(result as string, Guid.Empty.ToString("B"), StringComparison.Ordinal)) result = null; } break; } } #if DEV11_OR_LATER __VSHPROPID5 id5 = (__VSHPROPID5)propId; switch(id5) { case __VSHPROPID5.VSHPROPID_ProvisionalViewingStatus: result = ProvisionalViewingStatus; break; } #endif #if DEBUG if (propId != LastTracedProperty) { string trailer = (result == null) ? "null" : result.ToString(); LastTracedProperty = propId; // some basic filtering here... } #endif return result; } #if DEV11_OR_LATER public virtual __VSPROVISIONALVIEWINGSTATUS ProvisionalViewingStatus { get { return __VSPROVISIONALVIEWINGSTATUS.PVS_Disabled; } } #endif /// /// Sets the value of a property for a given property id /// /// the property id of the property to be set /// value of the property /// S_OK if succeeded [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "propid")] public virtual int SetProperty(int propid, object value) { __VSHPROPID id = (__VSHPROPID)propid; switch (id) { case __VSHPROPID.VSHPROPID_Expanded: this.IsExpanded = (bool)value; break; case __VSHPROPID.VSHPROPID_EditLabel: return SetEditLabel((string)value); default: break; } return VSConstants.S_OK; } /// /// Get a guid property /// /// property id for the guid property requested /// the requested guid /// S_OK if succeded [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "propid")] public virtual int GetGuidProperty(int propid, out Guid guid) { guid = Guid.Empty; if (propid == (int)__VSHPROPID.VSHPROPID_TypeGuid) { guid = this.ItemTypeGuid; } if (guid.Equals(Guid.Empty)) { return VSConstants.DISP_E_MEMBERNOTFOUND; } return VSConstants.S_OK; } public virtual bool CanAddFiles { get { return false; } } /// /// Set a guid property. /// /// property id of the guid property to be set /// the guid to be set /// E_NOTIMPL [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "propid")] public virtual int SetGuidProperty(int propid, ref Guid guid) { return VSConstants.E_NOTIMPL; } /// /// Called by the shell when a node has been renamed from the GUI /// /// /// E_NOTIMPL public virtual int SetEditLabel(string label) { return VSConstants.E_NOTIMPL; } /// /// Called by the shell to get the node caption when the user tries to rename from the GUI /// /// the node cation public virtual string GetEditLabel() { return this.Caption; } /// /// This method is called by the interface method GetMkDocument to specify the item moniker. /// /// The moniker for this item [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Mk")] public virtual string GetMkDocument() { return String.Empty; } /// /// Removes items from the hierarchy. Project overwrites this /// /// public virtual void Remove(bool removeFromStorage) { string documentToRemove = this.GetMkDocument(); // Ask Document tracker listeners if we can remove the item. string[] filesToBeDeleted = new string[1] { documentToRemove }; VSQUERYREMOVEFILEFLAGS[] queryRemoveFlags = this.GetQueryRemoveFileFlags(filesToBeDeleted); if (!this.ProjectMgr.Tracker.CanRemoveItems(filesToBeDeleted, queryRemoveFlags)) { return; } // Close the document if it has a manager. DocumentManager manager = this.GetDocumentManager(); if (manager != null) { if (manager.Close(!removeFromStorage ? __FRAMECLOSE.FRAMECLOSE_PromptSave : __FRAMECLOSE.FRAMECLOSE_NoSave) == VSConstants.E_ABORT) { // User cancelled operation in message box. return; } } if (removeFromStorage) { this.DeleteFromStorage(documentToRemove); } RemoveNonDocument(removeFromStorage); // Close the document window if opened. CloseDocumentWindow(this); // Notify document tracker listeners that we have removed the item. VSREMOVEFILEFLAGS[] removeFlags = this.GetRemoveFileFlags(filesToBeDeleted); Debug.Assert(removeFlags != null, "At least an empty array should be returned for the GetRemoveFileFlags"); this.ProjectMgr.Tracker.OnItemRemoved(documentToRemove, removeFlags[0]); // Notify hierarchy event listeners that items have been invalidated ProjectMgr.OnInvalidateItems(this); // Dispose the node now that is deleted. this.Dispose(true); } public void RemoveNonDocument(bool removeFromStorage) { // Check out the project file. if (!this.ProjectMgr.QueryEditProjectFile(false)) { throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED); } // Notify hierarchy event listeners that the file is going to be removed. ProjectMgr.OnItemDeleted(this); // Remove child if any before removing from the hierarchy for (HierarchyNode child = this.FirstChild; child != null; child = child.NextSibling) { child.Remove(removeFromStorage); } // the project node has no parentNode if (this.parentNode != null) { // Remove from the Hierarchy this.parentNode.RemoveChild(this); } this.itemNode.RemoveFromProjectFile(); } /// /// Returns the relational name which is defined as the first part of the caption until indexof NameRelationSeparator /// public virtual string GetRelationalName() { //Get the first part of the caption string[] partsOfParent = this.Caption.Split(new string[] { this.NameRelationSeparator }, StringSplitOptions.None); return partsOfParent[0]; } /// /// Returns the 'extension' of the relational name /// e.g. form1.resx returns .resx, form1.designer.cs returns .designer.cs /// /// The extension public virtual string GetRelationNameExtension() { return this.Caption.Substring(this.Caption.IndexOf(this.NameRelationSeparator, StringComparison.Ordinal)); } /// /// Close open document frame for a specific node. /// protected void CloseDocumentWindow(HierarchyNode node) { VsUtilities.ArgumentNotNull("node", node); // We walk the RDT looking for all running documents attached to this hierarchy and itemid. There // are cases where there may be two different editors (not views) open on the same document. IEnumRunningDocuments pEnumRdt; IVsRunningDocumentTable pRdt = this.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable; VsUtilities.CheckNotNull(pRdt); if (ErrorHandler.Succeeded(pRdt.GetRunningDocumentsEnum(out pEnumRdt))) { uint[] cookie = new uint[1]; uint fetched; uint saveOptions = (uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_NoSave; IVsHierarchy srpOurHier = node.projectMgr as IVsHierarchy; ErrorHandler.ThrowOnFailure(pEnumRdt.Reset()); while (VSConstants.S_OK == pEnumRdt.Next(1, cookie, out fetched)) { // Note we can pass NULL for all parameters we don't care about uint empty; string emptyStr; IntPtr ppunkDocData; IVsHierarchy srpHier; uint itemid = VSConstants.VSITEMID_NIL; ErrorHandler.ThrowOnFailure(pRdt.GetDocumentInfo( cookie[0], out empty, out empty, out empty, out emptyStr, out srpHier, out itemid, out ppunkDocData)); // Is this one of our documents? if (VsUtilities.IsSameComObject(srpOurHier, srpHier) && itemid == node.ID) { IVsSolution soln = GetService(typeof(SVsSolution)) as IVsSolution; ErrorHandler.ThrowOnFailure(soln.CloseSolutionElement(saveOptions, srpOurHier, cookie[0])); } if (ppunkDocData != IntPtr.Zero) Marshal.Release(ppunkDocData); } } } /// /// Redraws the state icon if the node is not excluded from source control. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Scc")] public virtual void UpdateSccStateIcons() { if (!this.ExcludeNodeFromScc) { ProjectMgr.ReDrawNode(this, UIHierarchyElement.SccState); } } /// /// To be overwritten by descendants. /// public virtual int SetEditLabel(string label, string relativePath) { throw new NotImplementedException(); } /// /// Called by the drag and drop implementation to ask the node /// which is being dragged/droped over which nodes should /// process the operation. /// This allows for dragging to a node that cannot contain /// items to let its parent accept the drop /// /// HierarchyNode that accept the drop handling public virtual HierarchyNode GetDragTargetHandlerNode() { return this; } /// /// Add a new Folder to the project hierarchy. /// /// S_OK if succeeded, otherwise an error [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] protected virtual int AddNewFolder() { // Check out the project file. if (!this.ProjectMgr.QueryEditProjectFile(false)) { throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED); } try { // Generate a new folder name string newFolderName; ErrorHandler.ThrowOnFailure(this.projectMgr.GenerateUniqueItemName(this.hierarchyId, String.Empty, String.Empty, out newFolderName)); // create the folder node, this will add it to MS build but we won't have the directory created yet. var folderNode = ProjectMgr.CreateFolderNode(Path.Combine(FullPathToChildren, newFolderName)); folderNode.IsBeingCreated = true; AddChild(folderNode); folderNode.ExpandItem(EXPANDFLAGS.EXPF_SelectItem); IVsUIShell shell = this.projectMgr.Site.GetService(typeof(SVsUIShell)) as IVsUIShell; // let the user rename the folder which will create the directory when finished int hr; object dummy = null; Guid cmdGroup = VsMenus.guidStandardCommandSet97; if (ErrorHandler.Failed(hr = shell.PostExecCommand(ref cmdGroup, (uint)VsCommands.Rename, 0, ref dummy))) { // make sure the directory is created... folderNode.OnCancelLabelEdit(); } } catch (COMException e) { Trace.WriteLine("Exception : " + e.Message); return e.ErrorCode; } return VSConstants.S_OK; } protected virtual int AddItemToHierarchy(HierarchyAddType addType) { string strFilter = String.Empty; int iDontShowAgain; uint uiFlags; IVsProject3 project = (IVsProject3)this.projectMgr; string strBrowseLocations = this.projectMgr.ProjectHome; System.Guid projectGuid = this.projectMgr.ProjectGuid; if (addType == HierarchyAddType.AddNewItem) { IVsAddProjectItemDlg addItemDialog = this.GetService(typeof(IVsAddProjectItemDlg)) as IVsAddProjectItemDlg; uiFlags = (uint)(__VSADDITEMFLAGS.VSADDITEM_AddNewItems | __VSADDITEMFLAGS.VSADDITEM_SuggestTemplateName | __VSADDITEMFLAGS.VSADDITEM_AllowHiddenTreeView); return addItemDialog.AddProjectItemDlg(this.hierarchyId, ref projectGuid, project, uiFlags, null, null, ref strBrowseLocations, ref strFilter, out iDontShowAgain); } else { uiFlags = (uint)(__VSADDITEMFLAGS.VSADDITEM_AddExistingItems | __VSADDITEMFLAGS.VSADDITEM_AllowMultiSelect | __VSADDITEMFLAGS.VSADDITEM_AllowStickyFilter); string filters = this.ProjectMgr.GetExistingFilesFilter(); using (System.Windows.Forms.OpenFileDialog dialog = new System.Windows.Forms.OpenFileDialog()) { dialog.InitialDirectory = strBrowseLocations; dialog.FileName = ""; dialog.Filter = filters + (filters.Length > 0 ? "|" : "") + SR.GetString(SR.AllFilesFilter); dialog.Multiselect = true; dialog.Title = SR.GetString(SR.AddExistingFile) + " - " + this.ProjectMgr.GetProjectProperty(ProjectFileConstants.Name); if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { foreach (var fileName in dialog.FileNames) { VSADDRESULT[] result = new VSADDRESULT[dialog.FileNames.Length]; project.AddItem(this.ID, VSADDITEMOPERATION.VSADDITEMOP_OPENFILE, fileName, 0, new string[] { fileName }, IntPtr.Zero, result); } } } //Once the dialog has been opened, the filters unfortunately don't change, that's why we don't use the usual dialog. /*RegisterFileTypes(filters); try { return addItemDialog.AddProjectItemDlg(this.hierarchyId, ref projectGuid, project, uiFlags, null, null, ref strBrowseLocations, ref strFilter, out iDontShowAgain); } finally { UnregisterFileTypes(filters); }*/ return VSConstants.S_OK; } } //Doesn't change the filters of the addItemDialog after the first time it has been shown. /*private void RegisterFileTypes(string filter) { if (string.IsNullOrWhiteSpace(filter)) return; string[] parts = filter.Split('|'); if (parts.Length % 2 != 0) throw new ArgumentException("Existing file filter doesn't contain a filter value for every filter name."); string name = this.ProjectMgr.Package.ApplicationRegistryRoot.Name; if (name.StartsWith(Registry.CurrentUser.Name)) { name = name.Substring(Registry.CurrentUser.Name.Length); name = name.TrimStart('\\'); } var context = Registry.CurrentUser.OpenSubKey(name, true); using (var key = context.CreateSubKey("Projects\\" + this.ProjectMgr.ProjectGuid.ToString("B") + "\\Filters", Win32.RegistryKeyPermissionCheck.ReadWriteSubTree)) { key.Flush(); for (int i = 0; i < parts.Length; i += 2) { string filterName = parts[i]; string filterValue = parts[i + 1].Replace(';', ','); using (var filterKey = key.CreateSubKey("/" + (i / 2 + 1).ToString())) { filterKey.SetValue(null, filterName + ";" + filterValue); filterKey.SetValue("SortPriority", i / 2 + 1); //filterKey.SetValue("CommonOpenFilesFilter", 1); } } } } private void UnregisterFileTypes(string filter) { if (string.IsNullOrWhiteSpace(filter)) return; string[] parts = filter.Split('|'); if (parts.Length % 2 != 0) throw new ArgumentException("Existing file filter doesn't contain a filter value for every filter name."); var context = this.ProjectMgr.Package.ApplicationRegistryRoot; using (var key = context.OpenSubKey("Projects\\" + this.ProjectMgr.ProjectGuid.ToString("B") + "\\Filters", true)) { for (int i = 0; i < parts.Length; i += 2) { key.DeleteSubKey("/" + (i / 2 + 1).ToString()); } } }*/ /// /// Overwritten in subclasses /// protected virtual void DoDefaultAction() { } /// /// Handles the exclude from project command. /// /// public virtual int ExcludeFromProject() { Debug.Assert(this.ProjectMgr != null, "The project item " + this.ToString() + " has not been initialised correctly. It has a null ProjectMgr"); this.Remove(false); return VSConstants.S_OK; } /// /// Handles the include in project command. /// /// public virtual int IncludeInProject(bool includeChildren) { return VSConstants.E_FAIL; } /// /// Handles the Show in Designer command. /// /// protected virtual int ShowInDesigner(IList selectedNodes) { return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; } /// /// Prepares a selected node for clipboard. /// It takes the the project reference string of this item and adds it to a stringbuilder. /// /// A stringbuilder. /// This method has to be public since seleceted nodes will call it. [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ClipBoard")] public virtual string PrepareSelectedNodesForClipBoard() { Debug.Assert(this.ProjectMgr != null, " No project mananager available for this node " + ToString()); Debug.Assert(this.ProjectMgr.ItemsDraggedOrCutOrCopied != null, " The itemsdragged list should have been initialized prior calling this method"); if (this.hierarchyId == VSConstants.VSITEMID_ROOT) { if (this.ProjectMgr.ItemsDraggedOrCutOrCopied != null) { this.ProjectMgr.ItemsDraggedOrCutOrCopied.Clear();// abort } return null; } if (this.ProjectMgr.ItemsDraggedOrCutOrCopied != null) { this.ProjectMgr.ItemsDraggedOrCutOrCopied.Add(this); } string projref = String.Empty; IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution; if (solution != null) { ErrorHandler.ThrowOnFailure(solution.GetProjrefOfItem(this.ProjectMgr, this.hierarchyId, out projref)); if (String.IsNullOrEmpty(projref)) { if (this.ProjectMgr.ItemsDraggedOrCutOrCopied != null) { this.ProjectMgr.ItemsDraggedOrCutOrCopied.Clear();// abort } return null; } } // Append the projectref and a null terminator to the string builder return projref + '\0'; } /// /// Returns the Cannonical Name /// /// Cannonical Name public virtual string GetCanonicalName() { return this.GetMkDocument(); } /// /// Factory method for the Document Manager object /// /// null object, since a hierarchy node does not know its kind of document /// Must be overriden by derived node classes if a document manager is needed public virtual DocumentManager GetDocumentManager() { return null; } /// /// Displays the context menu. /// /// list of selected nodes. /// contains the location (x,y) at which to show the menu. [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "pointer")] protected virtual int DisplayContextMenu(IList selectedNodes, IntPtr pointerToVariant) { if (selectedNodes == null || selectedNodes.Count == 0 || pointerToVariant == IntPtr.Zero) { return NativeMethods.OLECMDERR_E_NOTSUPPORTED; } int idmxStoredMenu = 0; Guid? menuGuid = null; foreach (HierarchyNode node in selectedNodes) { // We check here whether we have a multiple selection of // nodes of differing type. if (idmxStoredMenu == 0) { // First time through or single node case idmxStoredMenu = node.MenuCommandId; } else if (idmxStoredMenu != node.MenuCommandId) { // We have different node types. Check if any of the nodes is // the project node and set the menu accordingly. if (node.MenuCommandId == VsMenus.IDM_VS_CTXT_PROJNODE) { idmxStoredMenu = VsMenus.IDM_VS_CTXT_XPROJ_PROJITEM; } else { idmxStoredMenu = VsMenus.IDM_VS_CTXT_XPROJ_MULTIITEM; } } if (node.MenuCommandGuid != menuGuid) if (menuGuid == null) menuGuid = node.MenuCommandGuid; else //We can't handle different menu guids. return NativeMethods.E_FAIL; } if (menuGuid == null) menuGuid = VsMenus.guidSHLMainMenu; object variant = Marshal.GetObjectForNativeVariant(pointerToVariant); UInt32 pointsAsUint = (UInt32)variant; short x = (short)(pointsAsUint & 0x0000ffff); short y = (short)((pointsAsUint & 0xffff0000) / 0x10000); POINTS points = new POINTS(); points.x = x; points.y = y; return ShowContextMenu(idmxStoredMenu, menuGuid.Value, points); } /// /// Shows the specified context menu at a specified location. /// /// The context menu ID. /// The GUID of the menu group. /// The location at which to show the menu. protected virtual int ShowContextMenu(int menuId, Guid menuGroup, POINTS points) { IVsUIShell shell = this.projectMgr.Site.GetService(typeof(SVsUIShell)) as IVsUIShell; Debug.Assert(shell != null, "Could not get the ui shell from the project"); if (shell == null) { return VSConstants.E_FAIL; } POINTS[] pnts = new POINTS[1]; pnts[0].x = points.x; pnts[0].y = points.y; return shell.ShowContextMenu(0, ref menuGroup, menuId, pnts, (Microsoft.VisualStudio.OLE.Interop.IOleCommandTarget)ProjectMgr); } #region initiation of command execution /// /// Handles command execution. /// /// Unique identifier of the command group /// The command to be executed. /// Values describe how the object should execute the command. /// Pointer to a VARIANTARG structure containing input arguments. Can be NULL /// VARIANTARG structure to receive command output. Can be NULL. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cmdexecopt")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "n")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "pva")] public virtual int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { if (InvalidProject()) { return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; } if (cmdGroup == Guid.Empty) { return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; } else if (cmdGroup == VsMenus.guidVsUIHierarchyWindowCmds) { switch (cmd) { case (uint)VSConstants.VsUIHierarchyWindowCmdIds.UIHWCMDID_DoubleClick: case (uint)VSConstants.VsUIHierarchyWindowCmdIds.UIHWCMDID_EnterKey: this.DoDefaultAction(); return VSConstants.S_OK; case (uint)VSConstants.VsUIHierarchyWindowCmdIds.UIHWCMDID_CancelLabelEdit: this.OnCancelLabelEdit(); return VSConstants.S_OK; } return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; } else if (cmdGroup == VsMenus.guidStandardCommandSet97) { HierarchyNode nodeToAddTo = this.GetDragTargetHandlerNode(); switch ((VsCommands)cmd) { case VsCommands.AddNewItem: return nodeToAddTo.AddItemToHierarchy(HierarchyAddType.AddNewItem); case VsCommands.AddExistingItem: return nodeToAddTo.AddItemToHierarchy(HierarchyAddType.AddExistingItem); case VsCommands.NewFolder: return nodeToAddTo.AddNewFolder(); case VsCommands.Paste: return this.ProjectMgr.PasteFromClipboard(this); } } else if (cmdGroup == VsMenus.guidStandardCommandSet2K) { switch ((VsCommands2K)cmd) { case VsCommands2K.EXCLUDEFROMPROJECT: return this.ExcludeFromProject(); case VsCommands2K.INCLUDEINPROJECT: return this.IncludeInProject(true); case VsCommands2K.PROJSTARTDEBUG: return this.StartDebug(); } } return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; } #endregion #region query command handling /// /// Handles command status on a node. Should be overridden by descendant nodes. If a command cannot be handled then the base should be called. /// /// A unique identifier of the command group. The pguidCmdGroup parameter can be NULL to specify the standard group. /// The command to query status for. /// Pointer to an OLECMDTEXT structure in which to return the name and/or status information of a single command. Can be NULL to indicate that the caller does not require this information. /// An out parameter specifying the QueryStatusResult of the command. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "p")] public virtual int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { if (cmdGroup == VsMenus.guidStandardCommandSet97) { switch ((VsCommands)cmd) { case VsCommands.AddNewItem: case VsCommands.AddExistingItem: if (!IsNonMemberItem) { result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; return VSConstants.S_OK; } break; } } else if (cmdGroup == VsMenus.guidStandardCommandSet2K) { // http://social.msdn.microsoft.com/Forums/en/vsx/thread/f348aaed-cdcc-4709-9118-c0fd8b9e154d if ((VsCommands2K)cmd == VsCommands2K.SHOWALLFILES) { if (ProjectMgr.CanShowAllFiles) { if (ProjectMgr.IsShowingAllFiles) { result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED | QueryStatusResult.LATCHED; } else { result |= QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; } } else { result |= QueryStatusResult.NOTSUPPORTED | QueryStatusResult.INVISIBLE; } return VSConstants.S_OK; } } return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; } #endregion protected virtual int StartDebug() { return this.ProjectMgr.StartDebug(); } public virtual bool CanDeleteItem(__VSDELETEITEMOPERATION deleteOperation) { return this.ProjectMgr.CanProjectDeleteItems; } /// /// Overwrite this method to tell that you support the default icon for this node. /// /// protected virtual bool CanShowDefaultIcon() { return false; } /// /// Performs save as operation for an item after the save as dialog has been processed. /// /// A pointer to the rdt /// The newName of the item /// public virtual int AfterSaveItemAs(IntPtr docData, string newName) { throw new NotImplementedException(); } /// /// Invoked when the node receives UIHWCMDID_CancelLabelEdit hierarchy window command, which occurs /// when user cancels the label editing operation. /// protected virtual void OnCancelLabelEdit() { } /// /// The method that does the cleanup. /// /// Is the Dispose called by some public member, or it is called by from GC. protected virtual void Dispose(bool disposing) { if (this.isDisposed) { return; } if (disposing) { // This will dispose any subclassed project node that implements IDisposable. if (this.OleServiceProvider != null) { // Dispose the ole service provider object. this.OleServiceProvider.Dispose(); } } this.isDisposed = true; } /// /// Sets the VSQUERYADDFILEFLAGS that will be used to call the IVsTrackProjectDocumentsEvents2 OnQueryAddFiles /// /// The files to which an array of VSADDFILEFLAGS has to be specified. /// public virtual 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_NoFlags; } 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. /// public virtual 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_NoFlags; } 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. /// public virtual 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_NoFlags; } return queryRemoveFileFlags; } /// /// This method should be overridden to provide the list of files and associated flags for source control. /// /// The list of files to be placed under source control. /// The flags that are associated to the files. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Scc")] public virtual void GetSccFiles(IList files, IList flags) { if (this.ExcludeNodeFromScc || this.IsNonMemberItem) { return; } VsUtilities.ArgumentNotNull("files", files); VsUtilities.ArgumentNotNull("flags", flags); files.Add(this.GetMkDocument()); tagVsSccFilesFlags flagsToAdd = (this.firstChild != null && (this.firstChild is DependentFileNode)) ? tagVsSccFilesFlags.SFF_HasSpecialFiles : tagVsSccFilesFlags.SFF_NoFlags; flags.Add(flagsToAdd); } /// /// This method should be overridden to provide the list of special files and associated flags for source control. /// /// One of the file associated to the node. /// The list of files to be placed under source control. /// The flags that are associated to the files. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Scc")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "scc")] public virtual void GetSccSpecialFiles(string sccFile, IList files, IList flags) { if (this.ExcludeNodeFromScc) { return; } VsUtilities.ArgumentNotNull("files", files); VsUtilities.ArgumentNotNull("flags", flags); } public void OnInvalidateItems(HierarchyNode parent) { if (parent == null) { throw new ArgumentNullException("parent"); } ProjectNode projectNode = this.projectMgr; if (projectNode == this.projectMgr && (this.projectMgr.EventTriggeringFlag & ProjectNode.EventTriggering.DoNotTriggerHierarchyEvents) != 0) { return; } foreach (IVsHierarchyEvents sink in projectNode.EventSinks) { int result = sink.OnInvalidateItems(parent.hierarchyId); if (ErrorHandler.Failed(result) && result != VSConstants.E_NOTIMPL) { ErrorHandler.ThrowOnFailure(result); } } } /// /// Delete the item corresponding to the specified path from storage. /// /// Url of the item to delete public virtual void DeleteFromStorage(string path) { } /// /// Determines whether a file change should be ignored or not. /// /// Flag indicating whether or not to ignore changes (true to ignore changes). public virtual void IgnoreItemFileChanges(bool ignoreFlag) { } /// /// Called to determine whether a project item is reloadable. /// /// True if the project item is reloadable. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Reloadable")] public virtual bool IsItemReloadable() { return true; } /// /// Reloads an item. /// /// Reserved parameter defined at the IVsPersistHierarchyItem2::ReloadItem parameter. public virtual void ReloadItem(uint reserved) { } #endregion #region public methods /// /// Clears the cached node properties so that it will be recreated on the next request. /// public void ResetNodeProperties() { nodeProperties = null; } public void ExpandItem(EXPANDFLAGS flags) { if (ProjectMgr == null || ProjectMgr.Site == null) { return; } IVsUIHierarchyWindow2 windows = GetUIHierarchyWindow( ProjectMgr.Site, new Guid(ToolWindowGuids80.SolutionExplorer)) as IVsUIHierarchyWindow2; if (windows == null) { return; } ErrorHandler.ThrowOnFailure(windows.ExpandItem(ProjectMgr.GetOuterInterface(), ID, flags)); } public bool GetIsExpanded() { if (ProjectMgr == null || ProjectMgr.Site == null) { return false; } IVsUIHierarchyWindow2 windows = GetUIHierarchyWindow( ProjectMgr.Site, new Guid(ToolWindowGuids80.SolutionExplorer)) as IVsUIHierarchyWindow2; if (windows == null) { return false; } uint state; if (ErrorHandler.Succeeded(windows.GetItemState(ProjectMgr.GetOuterInterface(), ID, (uint)__VSHIERARCHYITEMSTATE.HIS_Expanded, out state))) { return state != 0; } return false; } /// /// Same as VsShellUtilities.GetUIHierarchyWindow, but it doesn't contain a useless cast to IVsWindowPane /// which fails on Dev10 with the solution explorer window. /// private static IVsUIHierarchyWindow GetUIHierarchyWindow(System.IServiceProvider serviceProvider, Guid guidPersistenceSlot) { VsUtilities.ArgumentNotNull("serviceProvider", serviceProvider); IVsUIShell service = serviceProvider.GetService(typeof(SVsUIShell)) as IVsUIShell; if (service == null) { throw new InvalidOperationException(); } object pvar = null; IVsWindowFrame ppWindowFrame = null; if (ErrorHandler.Succeeded(service.FindToolWindow(0, ref guidPersistenceSlot, out ppWindowFrame)) && ppWindowFrame != null && ErrorHandler.Succeeded(ppWindowFrame.GetProperty(-3001, out pvar))) { return (IVsUIHierarchyWindow)pvar; } return null; } /// /// AddChild - add a node, sorted in the right location. /// /// The node to add. public virtual void AddChild(HierarchyNode node) { VsUtilities.ArgumentNotNull("node", node); // make sure the node is in the map. Object nodeWithSameID = this.projectMgr.ItemIdMap[node.hierarchyId]; if (!Object.ReferenceEquals(node, nodeWithSameID as HierarchyNode)) { if (nodeWithSameID == null && node.ID <= this.ProjectMgr.ItemIdMap.Count) { // reuse our hierarchy id if possible. this.projectMgr.ItemIdMap.SetAt(node.hierarchyId, this); } else { throw new InvalidOperationException(); } } HierarchyNode previous = null; for (HierarchyNode n = this.firstChild; n != null; n = n.nextSibling) { if (this.ProjectMgr.CompareNodes(node, n) > 0) break; previous = n; } // insert "node" after "previous". if (previous != null) { node.nextSibling = previous.nextSibling; previous.nextSibling = node; } else { node.nextSibling = this.firstChild; this.firstChild = node; } node.parentNode = this; ProjectMgr.OnItemAdded(this, node); #if DEV10 // Dev10 won't check the IsHiddenItem flag when we add an item, and it'll just // make it visible no matter what. So we turn around and invalidate our parent // so it'll rescan the children items and see that we're not visible. if (!node.IsVisible) { ProjectMgr.OnInvalidateItems(this); } #endif } public object GetService(Type type) { VsUtilities.ArgumentNotNull("type", type); if (this.projectMgr == null || this.projectMgr.Site == null) return null; return this.projectMgr.Site.GetService(type); } #endregion #region IDisposable /// /// The IDispose interface Dispose method for disposing the object determinastically. /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } #endregion public virtual void Close() { DocumentManager manager = this.GetDocumentManager(); try { if (manager != null) { manager.Close(__FRAMECLOSE.FRAMECLOSE_PromptSave); } } catch { } finally { this.Dispose(true); } } public uint HierarchyId { get { return hierarchyId; } } #region helper methods /// /// Searches the immediate children of this node for a node which matches the specified predicate. /// public HierarchyNode FindImmediateChild(Func predicate) { for (HierarchyNode child = this.firstChild; child != null; child = child.NextSibling) { if (predicate(child)) { return child; } } return null; } /// /// Searches the immediate children of this node for a file who's moniker's filename (w/o path) matches /// the requested name. /// public HierarchyNode FindImmediateChildByName(string name) { for (HierarchyNode child = this.firstChild; child != null; child = child.NextSibling) { string filename = Path.GetFileName(CommonUtils.TrimEndSeparator(child.GetMkDocument())); if (String.Equals(filename, name, StringComparison.OrdinalIgnoreCase)) { return child; } } return null; } /// /// Recursively find all nodes of type T /// /// The type of hierachy node being serched for /// A list of nodes of type T [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] public void FindNodesOfType(List nodes) where T : HierarchyNode { for (HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling) { T nodeAsT = n as T; if (nodeAsT != null) { nodes.Add(nodeAsT); } n.FindNodesOfType(nodes); } } /// /// Recursively find all nodes of type T /// /// The type of hierachy node being serched for /// A list of nodes of type T [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] public IEnumerable EnumNodesOfType() where T : HierarchyNode { for (HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling) { T nodeAsT = n as T; if (nodeAsT != null) { yield return nodeAsT; } foreach(var node in n.EnumNodesOfType()) { yield return node; } } } #endregion private bool InvalidProject() { return this.projectMgr == null || this.projectMgr.IsClosed; } #region nested types /// /// DropEffect as defined in oleidl.h /// public enum DropEffect { None, Copy = 1, Move = 2, Link = 4 }; #endregion } }