/******************************************************************************************** Copyright (c) Microsoft Corporation All rights reserved. Microsoft Public License: This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 1. Definitions The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. A "contribution" is the original software, or any additions or changes to the software. A "contributor" is any person that distributes its contribution under this license. "Licensed patents" are a contributor's patent claims that read directly on its contribution. 2. Grant of Rights (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 3. Conditions and Limitations (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. ********************************************************************************************/ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text; using Microsoft.VisualStudio; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using IOleDataObject = Microsoft.VisualStudio.OLE.Interop.IDataObject; using OleConstants = Microsoft.VisualStudio.OLE.Interop.Constants; namespace Microsoft.VisualStudio.Project { /// /// Manages the CopyPaste and Drag and Drop scenarios for a Project. /// /// This is a partial class. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] public partial class ProjectNode : IVsUIHierWinClipboardHelperEvents { #region fields private uint copyPasteCookie; private DropDataType dropDataType; #endregion #region override of IVsHierarchyDropDataTarget methods /// /// Called as soon as the mouse drags an item over a new hierarchy or hierarchy window /// /// reference to interface IDataObject of the item being dragged /// Current state of the keyboard and the mouse modifier keys. See docs for a list of possible values /// Item identifier for the item currently being dragged /// On entry, a pointer to the current DropEffect. On return, must contain the new valid DropEffect /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public override int DragEnter(IOleDataObject pDataObject, uint grfKeyState, uint itemid, ref uint pdwEffect) { pdwEffect = (uint)DropEffect.None; if(this.SourceDraggedOrCutOrCopied) { return VSConstants.S_OK; } this.dropDataType = QueryDropDataType(pDataObject); if(this.dropDataType != DropDataType.None) { pdwEffect = (uint)this.QueryDropEffect(this.dropDataType, grfKeyState); } return VSConstants.S_OK; } /// /// Called when one or more items are dragged out of the hierarchy or hierarchy window, or when the drag-and-drop operation is cancelled or completed. /// /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public override int DragLeave() { this.dropDataType = DropDataType.None; return VSConstants.S_OK; } /// /// Called when one or more items are dragged over the target hierarchy or hierarchy window. /// /// Current state of the keyboard keys and the mouse modifier buttons. See /// Item identifier of the drop data target over which the item is being dragged /// On entry, reference to the value of the pdwEffect parameter of the IVsHierarchy object, identifying all effects that the hierarchy supports. /// On return, the pdwEffect parameter must contain one of the effect flags that indicate the result of the drop operation. For a list of pwdEffects values, see /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public override int DragOver(uint grfKeyState, uint itemid, ref uint pdwEffect) { pdwEffect = (uint)DropEffect.None; // Dragging items to a project that is being debugged is not supported // (see VSWhidbey 144785) DBGMODE dbgMode = VsShellUtilities.GetDebugMode(this.Site) & ~DBGMODE.DBGMODE_EncMask; if(dbgMode == DBGMODE.DBGMODE_Run || dbgMode == DBGMODE.DBGMODE_Break) { return VSConstants.S_OK; } if(this.isClosed || this.site == null) { return VSConstants.E_UNEXPECTED; } // We should also analyze if the node being dragged over can accept the drop. if(!this.CanTargetNodeAcceptDrop(itemid)) { return VSConstants.E_NOTIMPL; } if(this.dropDataType != DropDataType.None) { pdwEffect = (uint)this.QueryDropEffect(this.dropDataType, grfKeyState); } return VSConstants.S_OK; } /// /// Called when one or more items are dropped into the target hierarchy or hierarchy window when the mouse button is released. /// /// Reference to the IDataObject interface on the item being dragged. This data object contains the data being transferred in the drag-and-drop operation. /// If the drop occurs, then this data object (item) is incorporated into the target hierarchy or hierarchy window. /// Current state of the keyboard and the mouse modifier keys. See /// Item identifier of the drop data target over which the item is being dragged /// Visual effects associated with the drag-and drop-operation, such as a cursor, bitmap, and so on. /// The value of dwEffects passed to the source object via the OnDropNotify method is the value of pdwEffects returned by the Drop method /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public override int Drop(IOleDataObject pDataObject, uint grfKeyState, uint itemid, ref uint pdwEffect) { if(pDataObject == null) { return VSConstants.E_INVALIDARG; } pdwEffect = (uint)DropEffect.None; // Get the node that is being dragged over and ask it which node should handle this call HierarchyNode targetNode = NodeFromItemId(itemid); if(targetNode != null) { targetNode = targetNode.GetDragTargetHandlerNode(); } else { // There is no target node. The drop can not be completed. return VSConstants.S_FALSE; } int returnValue; try { DropDataType dropDataType = DropDataType.None; dropDataType = ProcessSelectionDataObject(pDataObject, targetNode); pdwEffect = (uint)this.QueryDropEffect(dropDataType, grfKeyState); // If it is a drop from windows and we get any kind of error we return S_FALSE and dropeffect none. This // prevents bogus messages from the shell from being displayed returnValue = (dropDataType != DropDataType.Shell) ? VSConstants.E_FAIL : VSConstants.S_OK; } catch(System.IO.FileNotFoundException e) { Trace.WriteLine("Exception : " + e.Message); if(!Utilities.IsInAutomationFunction(this.Site)) { string message = e.Message; string title = string.Empty; OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL; OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK; OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST; VsShellUtilities.ShowMessageBox(this.Site, title, message, icon, buttons, defaultButton); } returnValue = VSConstants.E_FAIL; } return returnValue; } #endregion #region override of IVsHierarchyDropDataSource2 methods /// /// Returns information about one or more of the items being dragged /// /// Pointer to a DWORD value describing the effects displayed while the item is being dragged, /// such as cursor icons that change during the drag-and-drop operation. /// For example, if the item is dragged over an invalid target point /// (such as the item's original location), the cursor icon changes to a circle with a line through it. /// Similarly, if the item is dragged over a valid target point, the cursor icon changes to a file or folder. /// Pointer to the IDataObject interface on the item being dragged. /// This data object contains the data being transferred in the drag-and-drop operation. /// If the drop occurs, then this data object (item) is incorporated into the target hierarchy or hierarchy window. /// Pointer to the IDropSource interface of the item being dragged. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public override int GetDropInfo(out uint pdwOKEffects, out IOleDataObject ppDataObject, out IDropSource ppDropSource) { //init out params pdwOKEffects = (uint)DropEffect.None; ppDataObject = null; ppDropSource = null; IOleDataObject dataObject = PackageSelectionDataObject(false); if(dataObject == null) { return VSConstants.E_NOTIMPL; } this.SourceDraggedOrCutOrCopied = true; pdwOKEffects = (uint)(DropEffect.Move | DropEffect.Copy); ppDataObject = dataObject; return VSConstants.S_OK; } /// /// Notifies clients that the dragged item was dropped. /// /// If true, then the dragged item was dropped on the target. If false, then the drop did not occur. /// Visual effects associated with the drag-and-drop operation, such as cursors, bitmaps, and so on. /// The value of dwEffects passed to the source object via OnDropNotify method is the value of pdwEffects returned by Drop method. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public override int OnDropNotify(int fDropped, uint dwEffects) { if(!this.SourceDraggedOrCutOrCopied) { return VSConstants.S_FALSE; } this.CleanupSelectionDataObject(fDropped != 0, false, dwEffects == (uint)DropEffect.Move); this.SourceDraggedOrCutOrCopied = false; return VSConstants.S_OK; } /// /// Allows the drag source to prompt to save unsaved items being dropped. /// Notifies the source hierarchy that information dragged from it is about to be dropped on a target. /// This method is called immediately after the mouse button is released on a drop. /// /// Reference to the IDataObject interface on the item being dragged. /// This data object contains the data being transferred in the drag-and-drop operation. /// If the drop occurs, then this data object (item) is incorporated into the hierarchy window of the new hierarchy. /// Current state of the keyboard and the mouse modifier keys. /// If true, then the drop is cancelled by the source hierarchy. If false, then the drop can continue. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public override int OnBeforeDropNotify(IOleDataObject o, uint dwEffect, out int fCancelDrop) { // If there is nothing to be dropped just return that drop should be cancelled. if(this.ItemsDraggedOrCutOrCopied == null) { fCancelDrop = 1; return VSConstants.S_OK; } fCancelDrop = 0; bool dirty = false; foreach(HierarchyNode node in this.ItemsDraggedOrCutOrCopied) { bool isDirty, isOpen, isOpenedByUs; uint docCookie; IVsPersistDocData ppIVsPersistDocData; DocumentManager manager = node.GetDocumentManager(); if(manager != null) { manager.GetDocInfo(out isOpen, out isDirty, out isOpenedByUs, out docCookie, out ppIVsPersistDocData); if(isDirty && isOpenedByUs) { dirty = true; break; } } } // if there are no dirty docs we are ok to proceed if(!dirty) { return VSConstants.S_OK; } // Prompt to save if there are dirty docs string message = SR.GetString(SR.SaveModifiedDocuments, CultureInfo.CurrentUICulture); string title = string.Empty; OLEMSGICON icon = OLEMSGICON.OLEMSGICON_WARNING; OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_YESNOCANCEL; OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST; int result = VsShellUtilities.ShowMessageBox(Site, title, message, icon, buttons, defaultButton); switch(result) { case NativeMethods.IDYES: break; case NativeMethods.IDNO: return VSConstants.S_OK; case NativeMethods.IDCANCEL: goto default; default: fCancelDrop = 1; return VSConstants.S_OK; } // Save all dirty documents foreach(HierarchyNode node in this.ItemsDraggedOrCutOrCopied) { DocumentManager manager = node.GetDocumentManager(); if(manager != null) { manager.Save(true); } } return VSConstants.S_OK; } #endregion #region IVsUIHierWinClipboardHelperEvents Members /// /// Called after your cut/copied items has been pasted /// ///If true, then the IDataObject has been successfully pasted into a target hierarchy. /// If false, then the cut or copy operation was cancelled. /// Visual effects associated with the drag and drop operation, such as cursors, bitmaps, and so on. /// These should be the same visual effects used in OnDropNotify /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public virtual int OnPaste(int wasCut, uint dropEffect) { if(!this.SourceDraggedOrCutOrCopied) { return VSConstants.S_FALSE; } if(dropEffect == (uint)DropEffect.None) { return OnClear(wasCut); } this.CleanupSelectionDataObject(false, wasCut != 0, dropEffect == (uint)DropEffect.Move); this.SourceDraggedOrCutOrCopied = false; return VSConstants.S_OK; } /// /// Called when your cut/copied operation is canceled /// /// This flag informs the source that the Cut method was called (true), /// rather than Copy (false), so the source knows whether to "un-cut-highlight" the items that were cut. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. public virtual int OnClear(int wasCut) { if(!this.SourceDraggedOrCutOrCopied) { return VSConstants.S_FALSE; } this.CleanupSelectionDataObject(false, wasCut != 0, false, true); this.SourceDraggedOrCutOrCopied = false; return VSConstants.S_OK; } #endregion #region virtual methods /// /// Determines if a node can accept drop opertaion. /// /// The id of the node. /// true if the node acceots drag operation. protected internal virtual bool CanTargetNodeAcceptDrop(uint itemId) { HierarchyNode targetNode = NodeFromItemId(itemId); if(targetNode is ReferenceContainerNode || targetNode is ReferenceNode) { return false; } else { return true; } } /// /// Returns a dataobject from selected nodes /// /// boolean that defines if the selected items must be cut /// data object for selected items internal virtual DataObject PackageSelectionDataObject(bool cutHighlightItems) { this.CleanupSelectionDataObject(false, false, false); StringBuilder sb = new StringBuilder(); DataObject dataObject = null; try { IList selectedNodes = this.GetSelectedNodes(); if(selectedNodes != null) { this.InstantiateItemsDraggedOrCutOrCopiedList(); StringBuilder selectionContent = null; // If there is a selection package the data if(selectedNodes.Count > 1) { foreach(HierarchyNode node in selectedNodes) { selectionContent = node.PrepareSelectedNodesForClipBoard(); if(selectionContent != null) { sb.Append(selectionContent); } } } else if(selectedNodes.Count == 1) { HierarchyNode selectedNode = selectedNodes[0]; selectionContent = selectedNode.PrepareSelectedNodesForClipBoard(); if(selectionContent != null) { sb.Append(selectionContent); } } } // Add the project items first. IntPtr ptrToItems = this.PackageSelectionData(sb, false); if(ptrToItems == IntPtr.Zero) { return null; } FORMATETC fmt = DragDropHelper.CreateFormatEtc(DragDropHelper.CF_VSSTGPROJECTITEMS); dataObject = new DataObject(); dataObject.SetData(fmt, ptrToItems); // Now add the project path that sourced data. We just write the project file path. IntPtr ptrToProjectPath = this.PackageSelectionData(new StringBuilder(this.GetMkDocument()), true); if(ptrToProjectPath != IntPtr.Zero) { dataObject.SetData(DragDropHelper.CreateFormatEtc(DragDropHelper.CF_VSPROJECTCLIPDESCRIPTOR), ptrToProjectPath); } if (cutHighlightItems) { bool first = true; IVsUIHierarchyWindow w = UIHierarchyUtilities.GetUIHierarchyWindow(this.site, HierarchyNode.SolutionExplorer); // This happens in the context of cutting multiple items from the project. // Since we are already in solution explorer, it is extremely unlikely that we get a null return. // If we do, the icons for the cut items will not fade. The cut operation will still succeed. if (w != null) { foreach (HierarchyNode node in this.ItemsDraggedOrCutOrCopied) { ErrorHandler.ThrowOnFailure(w.ExpandItem(this.InteropSafeIVsUIHierarchy, node.ID, first ? EXPANDFLAGS.EXPF_CutHighlightItem : EXPANDFLAGS.EXPF_AddCutHighlightItem)); first = false; } } } } catch(COMException e) { Trace.WriteLine("Exception : " + e.Message); dataObject = null; } return dataObject; } /// /// This is used to recursively add a folder from an other project. /// Note that while we copy the folder content completely, we only /// add to the project items which are part of the source project. /// /// Project reference (from data object) using the format: {Guid}|project|folderPath /// Node to add the new folder to protected internal virtual void AddFolderFromOtherProject(string folderToAdd, HierarchyNode targetNode) { if(String.IsNullOrEmpty(folderToAdd)) throw new ArgumentNullException("folderToAdd"); if(targetNode == null) throw new ArgumentNullException("targetNode"); // Split the reference in its 3 parts int index1 = Guid.Empty.ToString("B").Length; if(index1 + 1 >= folderToAdd.Length) throw new ArgumentOutOfRangeException("folderToAdd"); // Get the Guid string guidString = folderToAdd.Substring(1, index1 - 2); Guid projectInstanceGuid = new Guid(guidString); // Get the project path int index2 = folderToAdd.IndexOf('|', index1 + 1); if(index2 < 0 || index2 + 1 >= folderToAdd.Length) throw new ArgumentOutOfRangeException("folderToAdd"); // Finally get the source path string folder = folderToAdd.Substring(index2 + 1); // Get the target path string folderName = Path.GetFileName(Path.GetDirectoryName(folder)); string targetPath = Path.Combine(GetBaseDirectoryForAddingFiles(targetNode), folderName); // Recursively copy the directory to the new location Utilities.RecursivelyCopyDirectory(folder, targetPath); // Retrieve the project from which the items are being copied IVsHierarchy sourceHierarchy; IVsSolution solution = (IVsSolution)GetService(typeof(SVsSolution)); ErrorHandler.ThrowOnFailure(solution.GetProjectOfGuid(ref projectInstanceGuid, out sourceHierarchy)); // Then retrieve the item ID of the item to copy uint itemID = VSConstants.VSITEMID_ROOT; ErrorHandler.ThrowOnFailure(sourceHierarchy.ParseCanonicalName(folder, out itemID)); // Ensure we don't end up in an endless recursion if(Utilities.IsSameComObject(this.InteropSafeIVsHierarchy, sourceHierarchy)) { HierarchyNode cursorNode = targetNode; while(cursorNode != null) { if(String.Compare(folder, cursorNode.GetMkDocument(), StringComparison.OrdinalIgnoreCase) == 0) throw new Exception(); cursorNode = cursorNode.Parent; } } // Now walk the source project hierarchy to see which node needs to be added. WalkSourceProjectAndAdd(sourceHierarchy, itemID, targetNode, false); } /// /// Recursive method that walk a hierarchy and add items it find to our project. /// Note that this is meant as an helper to the Copy&Paste/Drag&Drop functionality. /// /// Hierarchy to walk /// Item ID where to start walking the hierarchy /// Node to start adding to /// Typically false on first call and true after that protected virtual void WalkSourceProjectAndAdd(IVsHierarchy sourceHierarchy, uint itemId, HierarchyNode targetNode, bool addSiblings) { if (sourceHierarchy == null) { throw new ArgumentNullException("sourceHierarchy"); } // Before we start the walk, add the current node object variant = null; HierarchyNode newNode = targetNode; if(itemId != VSConstants.VSITEMID_NIL) { // Calculate the corresponding path in our project string source; ErrorHandler.ThrowOnFailure(((IVsProject)sourceHierarchy).GetMkDocument(itemId, out source)); string name = Path.GetFileName(source.TrimEnd(new char[] { '/', '\\' })); string targetPath = Path.Combine(GetBaseDirectoryForAddingFiles(targetNode), name); // See if this is a linked item (file can be linked, not folders) ErrorHandler.ThrowOnFailure(sourceHierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_BrowseObject, out variant), VSConstants.E_NOTIMPL); VSLangProj.FileProperties fileProperties = variant as VSLangProj.FileProperties; if(fileProperties != null && fileProperties.IsLink) { // Since we don't support linked item, we make a copy of the file into our storage where it would have been linked File.Copy(source, targetPath, true); } newNode = AddNodeIfTargetExistInStorage(targetNode, name, targetPath); // Start with child nodes (depth first) variant = null; ErrorHandler.ThrowOnFailure(sourceHierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_FirstVisibleChild, out variant)); uint currentItemID = (uint)(int)variant; WalkSourceProjectAndAdd(sourceHierarchy, currentItemID, newNode, true); if(addSiblings) { // Then look at siblings currentItemID = itemId; while(currentItemID != VSConstants.VSITEMID_NIL) { variant = null; ErrorHandler.ThrowOnFailure(sourceHierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_NextVisibleSibling, out variant)); currentItemID = (uint)(int)variant; WalkSourceProjectAndAdd(sourceHierarchy, currentItemID, targetNode, true); } } } } /// /// Add an existing item (file/folder) to the project if it already exist in our storage. /// /// Node to that this item to /// Name of the item being added /// Path of the item being added /// Node that was added protected virtual HierarchyNode AddNodeIfTargetExistInStorage(HierarchyNode parentNode, string name, string targetPath) { if (parentNode == null) { return null; } HierarchyNode newNode = parentNode; // If the file/directory exist, add a node for it if(File.Exists(targetPath)) { VSADDRESULT[] result = new VSADDRESULT[1]; ErrorHandler.ThrowOnFailure(this.AddItem(parentNode.ID, VSADDITEMOPERATION.VSADDITEMOP_OPENFILE, name, 1, new string[] { targetPath }, IntPtr.Zero, result)); if(result[0] != VSADDRESULT.ADDRESULT_Success) throw new Exception(); newNode = this.FindChild(targetPath); if(newNode == null) throw new Exception(); } else if(Directory.Exists(targetPath)) { newNode = this.CreateFolderNodes(targetPath); } return newNode; } #endregion #region non-virtual methods /// /// Handle the Cut operation to the clipboard /// protected internal override int CutToClipboard() { int returnValue = (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; try { this.RegisterClipboardNotifications(true); // Create our data object and change the selection to show item(s) being cut IOleDataObject dataObject = this.PackageSelectionDataObject(true); if(dataObject != null) { this.SourceDraggedOrCutOrCopied = true; // Add our cut item(s) to the clipboard ErrorHandler.ThrowOnFailure(UnsafeNativeMethods.OleSetClipboard(dataObject)); // Inform VS (UiHierarchyWindow) of the cut IVsUIHierWinClipboardHelper clipboardHelper = (IVsUIHierWinClipboardHelper)GetService(typeof(SVsUIHierWinClipboardHelper)); if(clipboardHelper == null) { return VSConstants.E_FAIL; } returnValue = ErrorHandler.ThrowOnFailure(clipboardHelper.Cut(dataObject)); } } catch(COMException e) { Trace.WriteLine("Exception : " + e.Message); returnValue = e.ErrorCode; } return returnValue; } /// /// Handle the Copy operation to the clipboard /// protected internal override int CopyToClipboard() { int returnValue = (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; try { this.RegisterClipboardNotifications(true); // Create our data object and change the selection to show item(s) being copy IOleDataObject dataObject = this.PackageSelectionDataObject(false); if(dataObject != null) { this.SourceDraggedOrCutOrCopied = true; // Add our copy item(s) to the clipboard ErrorHandler.ThrowOnFailure(UnsafeNativeMethods.OleSetClipboard(dataObject)); // Inform VS (UiHierarchyWindow) of the copy IVsUIHierWinClipboardHelper clipboardHelper = (IVsUIHierWinClipboardHelper)GetService(typeof(SVsUIHierWinClipboardHelper)); if(clipboardHelper == null) { return VSConstants.E_FAIL; } returnValue = ErrorHandler.ThrowOnFailure(clipboardHelper.Copy(dataObject)); } } catch(COMException e) { Trace.WriteLine("Exception : " + e.Message); returnValue = e.ErrorCode; } catch(ArgumentException e) { Trace.WriteLine("Exception : " + e.Message); returnValue = Marshal.GetHRForException(e); } return returnValue; } /// /// Handle the Paste operation to a targetNode /// protected internal override int PasteFromClipboard(HierarchyNode targetNode) { int returnValue = (int)OleConstants.OLECMDERR_E_NOTSUPPORTED; if (targetNode == null) { return VSConstants.E_INVALIDARG; } //Get the clipboardhelper service and use it after processing dataobject IVsUIHierWinClipboardHelper clipboardHelper = (IVsUIHierWinClipboardHelper)GetService(typeof(SVsUIHierWinClipboardHelper)); if(clipboardHelper == null) { return VSConstants.E_FAIL; } try { //Get dataobject from clipboard IOleDataObject dataObject = null; ErrorHandler.ThrowOnFailure(UnsafeNativeMethods.OleGetClipboard(out dataObject)); if(dataObject == null) { return VSConstants.E_UNEXPECTED; } DropEffect dropEffect = DropEffect.None; DropDataType dropDataType = DropDataType.None; try { dropDataType = this.ProcessSelectionDataObject(dataObject, targetNode.GetDragTargetHandlerNode()); dropEffect = this.QueryDropEffect(dropDataType, 0); } catch(ExternalException e) { Trace.WriteLine("Exception : " + e.Message); // If it is a drop from windows and we get any kind of error ignore it. This // prevents bogus messages from the shell from being displayed if(dropDataType != DropDataType.Shell) { throw; } } finally { // Inform VS (UiHierarchyWindow) of the paste returnValue = clipboardHelper.Paste(dataObject, (uint)dropEffect); } } catch(COMException e) { Trace.WriteLine("Exception : " + e.Message); returnValue = e.ErrorCode; } return returnValue; } /// /// Determines if the paste command should be allowed. /// /// protected internal override bool AllowPasteCommand() { IOleDataObject dataObject = null; try { ErrorHandler.ThrowOnFailure(UnsafeNativeMethods.OleGetClipboard(out dataObject)); if(dataObject == null) { return false; } // First see if this is a set of storage based items FORMATETC format = DragDropHelper.CreateFormatEtc((ushort)DragDropHelper.CF_VSSTGPROJECTITEMS); if(dataObject.QueryGetData(new FORMATETC[] { format }) == VSConstants.S_OK) return true; // Try reference based items format = DragDropHelper.CreateFormatEtc((ushort)DragDropHelper.CF_VSREFPROJECTITEMS); if(dataObject.QueryGetData(new FORMATETC[] { format }) == VSConstants.S_OK) return true; // Try windows explorer files format format = DragDropHelper.CreateFormatEtc((ushort)NativeMethods.CF_HDROP); return (dataObject.QueryGetData(new FORMATETC[] { format }) == VSConstants.S_OK); } // We catch External exceptions since it might be that it is not our data on the clipboard. catch(ExternalException e) { Trace.WriteLine("Exception :" + e.Message); return false; } } /// /// Register/Unregister for Clipboard events for the UiHierarchyWindow (solution explorer) /// /// true for register, false for unregister protected internal override void RegisterClipboardNotifications(bool register) { // Get the UiHierarchy window clipboard helper service IVsUIHierWinClipboardHelper clipboardHelper = (IVsUIHierWinClipboardHelper)GetService(typeof(SVsUIHierWinClipboardHelper)); if(clipboardHelper == null) { return; } if(register && this.copyPasteCookie == 0) { // Register ErrorHandler.ThrowOnFailure(clipboardHelper.AdviseClipboardHelperEvents(this.InteropSafeIVsUIHierWinClipboardHelperEvents, out this.copyPasteCookie)); Debug.Assert(this.copyPasteCookie != 0, "AdviseClipboardHelperEvents returned an invalid cookie"); } else if(!register && this.copyPasteCookie != 0) { // Unregister ErrorHandler.ThrowOnFailure(clipboardHelper.UnadviseClipboardHelperEvents(this.copyPasteCookie)); this.copyPasteCookie = 0; } } /// /// Process dataobject from Drag/Drop/Cut/Copy/Paste operation /// /// The targetNode is set if the method is called from a drop operation, otherwise it is null internal DropDataType ProcessSelectionDataObject(IOleDataObject dataObject, HierarchyNode targetNode) { DropDataType dropDataType = DropDataType.None; bool isWindowsFormat = false; // Try to get it as a directory based project. List filesDropped = DragDropHelper.GetDroppedFiles(DragDropHelper.CF_VSSTGPROJECTITEMS, dataObject, out dropDataType); if(filesDropped.Count == 0) { filesDropped = DragDropHelper.GetDroppedFiles(DragDropHelper.CF_VSREFPROJECTITEMS, dataObject, out dropDataType); } if(filesDropped.Count == 0) { filesDropped = DragDropHelper.GetDroppedFiles(NativeMethods.CF_HDROP, dataObject, out dropDataType); isWindowsFormat = (filesDropped.Count > 0); } if(dropDataType != DropDataType.None && filesDropped.Count > 0) { string[] filesDroppedAsArray = filesDropped.ToArray(); HierarchyNode node = (targetNode == null) ? this : targetNode; // For directory based projects the content of the clipboard is a double-NULL terminated list of Projref strings. if(isWindowsFormat) { // This is the code path when source is windows explorer VSADDRESULT[] vsaddresults = new VSADDRESULT[1]; vsaddresults[0] = VSADDRESULT.ADDRESULT_Failure; int addResult = AddItem(node.ID, VSADDITEMOPERATION.VSADDITEMOP_OPENFILE, null, (uint)filesDropped.Count, filesDroppedAsArray, IntPtr.Zero, vsaddresults); if(addResult != VSConstants.S_OK && addResult != VSConstants.S_FALSE && addResult != (int)OleConstants.OLECMDERR_E_CANCELED && vsaddresults[0] != VSADDRESULT.ADDRESULT_Success) { ErrorHandler.ThrowOnFailure(addResult); } return dropDataType; } else { if(AddFilesFromProjectReferences(node, filesDroppedAsArray)) { return dropDataType; } } } // If we reached this point then the drop data must be set to None. // Otherwise the OnPaste will be called with a valid DropData and that would actually delete the item. return DropDataType.None; } /// /// Get the dropdatatype from the dataobject /// /// The dataobject to be analysed for its format /// dropdatatype or none if dataobject does not contain known format internal static DropDataType QueryDropDataType(IOleDataObject pDataObject) { if(pDataObject == null) { return DropDataType.None; } // known formats include File Drops (as from WindowsExplorer), // VSProject Reference Items and VSProject Storage Items. FORMATETC fmt = DragDropHelper.CreateFormatEtc(NativeMethods.CF_HDROP); if(DragDropHelper.QueryGetData(pDataObject, ref fmt) == VSConstants.S_OK) { return DropDataType.Shell; } fmt.cfFormat = DragDropHelper.CF_VSREFPROJECTITEMS; if(DragDropHelper.QueryGetData(pDataObject, ref fmt) == VSConstants.S_OK) { // Data is from a Ref-based project. return DropDataType.VsRef; } fmt.cfFormat = DragDropHelper.CF_VSSTGPROJECTITEMS; if(DragDropHelper.QueryGetData(pDataObject, ref fmt) == VSConstants.S_OK) { return DropDataType.VsStg; } return DropDataType.None; } /// /// Returns the drop effect. /// /// /// // A directory based project should perform as follow: /// NO MODIFIER /// - COPY if not from current hierarchy, /// - MOVE if from current hierarchy /// SHIFT DRAG - MOVE /// CTRL DRAG - COPY /// CTRL-SHIFT DRAG - NO DROP (used for reference based projects only) /// internal DropEffect QueryDropEffect(DropDataType dropDataType, uint grfKeyState) { //Validate the dropdatatype if((dropDataType != DropDataType.Shell) && (dropDataType != DropDataType.VsRef) && (dropDataType != DropDataType.VsStg)) { return DropEffect.None; } // CTRL-SHIFT if((grfKeyState & NativeMethods.MK_CONTROL) != 0 && (grfKeyState & NativeMethods.MK_SHIFT) != 0) { // Because we are not referenced base, we don't support link return DropEffect.None; } // CTRL if((grfKeyState & NativeMethods.MK_CONTROL) != 0) return DropEffect.Copy; // SHIFT if((grfKeyState & NativeMethods.MK_SHIFT) != 0) return DropEffect.Move; // no modifier if(this.SourceDraggedOrCutOrCopied) { return DropEffect.Move; } else { return DropEffect.Copy; } } internal void CleanupSelectionDataObject(bool dropped, bool cut, bool moved) { this.CleanupSelectionDataObject(dropped, cut, moved, false); } /// /// After a drop or paste, will use the dwEffects /// to determine whether we need to clean up the source nodes or not. If /// justCleanup is set, it only does the cleanup work. /// internal void CleanupSelectionDataObject(bool dropped, bool cut, bool moved, bool justCleanup) { if(this.ItemsDraggedOrCutOrCopied == null || this.ItemsDraggedOrCutOrCopied.Count == 0) { return; } try { IVsUIHierarchyWindow w = UIHierarchyUtilities.GetUIHierarchyWindow(this.site, HierarchyNode.SolutionExplorer); foreach(HierarchyNode node in this.ItemsDraggedOrCutOrCopied) { if((moved && (cut || dropped) && !justCleanup)) { // do not close it if the doc is dirty or we do not own it bool isDirty, isOpen, isOpenedByUs; uint docCookie; IVsPersistDocData ppIVsPersistDocData; DocumentManager manager = node.GetDocumentManager(); if(manager != null) { manager.GetDocInfo(out isOpen, out isDirty, out isOpenedByUs, out docCookie, out ppIVsPersistDocData); if(isDirty || (isOpen && !isOpenedByUs)) { continue; } // close it if opened if(isOpen) { manager.Close(__FRAMECLOSE.FRAMECLOSE_NoSave); } } node.Remove(true); } else if(w != null) { ErrorHandler.ThrowOnFailure(w.ExpandItem(this.InteropSafeIVsUIHierarchy, node.ID, EXPANDFLAGS.EXPF_UnCutHighlightItem)); } } } finally { try { // Now delete the memory allocated by the packaging of datasources. // If we just did a cut, or we are told to cleanup, then we need to free the data object. Otherwise, we leave it // alone so that you can continue to paste the data in new locations. if(moved || cut || justCleanup) { this.ItemsDraggedOrCutOrCopied.Clear(); this.CleanAndFlushClipboard(); } } finally { this.dropDataType = DropDataType.None; } } } /// /// Moves files from one part of our project to another. /// /// the targetHandler node /// List of projectref string /// true if succeeded internal bool AddFilesFromProjectReferences(HierarchyNode targetNode, string[] projectReferences) { //Validate input if(projectReferences == null) { throw new ArgumentException(SR.GetString(SR.InvalidParameter, CultureInfo.CurrentUICulture), "projectReferences"); } if(targetNode == null) { throw new InvalidOperationException(); } //Iteratively add files from projectref foreach(string projectReference in projectReferences) { if(projectReference == null) { // bad projectref, bail out return false; } if(projectReference.EndsWith("/", StringComparison.Ordinal) || projectReference.EndsWith("\\", StringComparison.Ordinal)) { AddFolderFromOtherProject(projectReference, targetNode); } else if(!AddFileToNodeFromProjectReference(projectReference, targetNode)) { return false; } } return true; } #endregion #region private helper methods /// /// Empties all the data structures added to the clipboard and flushes the clipboard. /// private void CleanAndFlushClipboard() { IOleDataObject oleDataObject = null; ErrorHandler.ThrowOnFailure(UnsafeNativeMethods.OleGetClipboard(out oleDataObject)); if(oleDataObject == null) { return; } string sourceProjectPath = DragDropHelper.GetSourceProjectPath(oleDataObject); if(!String.IsNullOrEmpty(sourceProjectPath) && NativeMethods.IsSamePath(sourceProjectPath, this.GetMkDocument())) { ErrorHandler.ThrowOnFailure(UnsafeNativeMethods.OleFlushClipboard()); int clipboardOpened = 0; try { ErrorHandler.ThrowOnFailure(clipboardOpened = UnsafeNativeMethods.OpenClipboard(IntPtr.Zero)); ErrorHandler.ThrowOnFailure(UnsafeNativeMethods.EmptyClipboard()); } finally { if(clipboardOpened == 1) { ErrorHandler.ThrowOnFailure(UnsafeNativeMethods.CloseClipboard()); } } } } private IntPtr PackageSelectionData(StringBuilder sb, bool addEndFormatDelimiter) { if(sb == null || sb.ToString().Length == 0 || this.ItemsDraggedOrCutOrCopied.Count == 0) { return IntPtr.Zero; } // Double null at end. if(addEndFormatDelimiter) { if(sb.ToString()[sb.Length - 1] != '\0') { sb.Append('\0'); } } // We request unmanaged permission to execute the below. new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); _DROPFILES df = new _DROPFILES(); int dwSize = Marshal.SizeOf(df); Int16 wideChar = 0; int dwChar = Marshal.SizeOf(wideChar); int structSize = dwSize + ((sb.Length + 1) * dwChar); IntPtr ptr = Marshal.AllocHGlobal(structSize); df.pFiles = dwSize; df.fWide = 1; IntPtr data = IntPtr.Zero; try { data = UnsafeNativeMethods.GlobalLock(ptr); Marshal.StructureToPtr(df, data, false); IntPtr strData = new IntPtr((long)data + dwSize); DragDropHelper.CopyStringToHGlobal(sb.ToString(), strData, structSize); } finally { if(data != IntPtr.Zero) UnsafeNativeMethods.GlobalUnLock(data); } return ptr; } #endregion } }