anx.framework/Visual Studio/MPF11/Dev11/Src/CSharp/ProjectNode.CopyPaste.cs
Konstantin Koch 8287c54432 Included the Visual Studio extension and made the necessary changes to make it run.
Replaced the old VS templates with ones that offer more flexiblity.
Started replacing the Content Project for the samples with our custom project type.
Inlcuded a basic not yet working AssimpImporter.
2015-04-08 14:50:03 +02:00

1636 lines
78 KiB
C#

/* ****************************************************************************
*
* 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.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using System.Windows;
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 {
/// <summary>
/// Manages the CopyPaste and Drag and Drop scenarios for a Project.
/// </summary>
/// <remarks>This is a partial class.</remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
public partial class ProjectNode : IVsUIHierWinClipboardHelperEvents {
private uint copyPasteCookie;
private DropDataType _dropType;
#region override of IVsHierarchyDropDataTarget methods
/// <summary>
/// Called as soon as the mouse drags an item over a new hierarchy or hierarchy window
/// </summary>
/// <param name="pDataObject">reference to interface IDataObject of the item being dragged</param>
/// <param name="grfKeyState">Current state of the keyboard and the mouse modifier keys. See docs for a list of possible values</param>
/// <param name="itemid">Item identifier for the item currently being dragged</param>
/// <param name="pdwEffect">On entry, a pointer to the current DropEffect. On return, must contain the new valid DropEffect</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code.</returns>
public int DragEnter(IOleDataObject pDataObject, uint grfKeyState, uint itemid, ref uint pdwEffect) {
pdwEffect = (uint)DropEffect.None;
var item = NodeFromItemId(itemid);
if (item.GetDragTargetHandlerNode().CanAddFiles) {
_dropType = QueryDropDataType(pDataObject);
if (_dropType != DropDataType.None) {
pdwEffect = (uint)QueryDropEffect(grfKeyState);
}
}
return VSConstants.S_OK;
}
/// <summary>
/// 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.
/// </summary>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code.</returns>
public int DragLeave() {
_dropType = DropDataType.None;
return VSConstants.S_OK;
}
/// <summary>
/// Called when one or more items are dragged over the target hierarchy or hierarchy window.
/// </summary>
/// <param name="grfKeyState">Current state of the keyboard keys and the mouse modifier buttons. See <seealso cref="IVsHierarchyDropDataTarget"/></param>
/// <param name="itemid">Item identifier of the drop data target over which the item is being dragged</param>
/// <param name="pdwEffect"> 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 <seealso cref="DragEnter"/></param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code.</returns>
public 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;
}
// TODO: We should also analyze if the node being dragged over can accept the drop.
pdwEffect = (uint)QueryDropEffect(grfKeyState);
return VSConstants.S_OK;
}
/// <summary>
/// Called when one or more items are dropped into the target hierarchy or hierarchy window when the mouse button is released.
/// </summary>
/// <param name="pDataObject">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.</param>
/// <param name="grfKeyState">Current state of the keyboard and the mouse modifier keys. See <seealso cref="IVsHierarchyDropDataTarget"/></param>
/// <param name="itemid">Item identifier of the drop data target over which the item is being dragged</param>
/// <param name="pdwEffect">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</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
public 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) {
// There is no target node. The drop can not be completed.
return VSConstants.S_FALSE;
}
int returnValue;
try {
DropDataType dropDataType = DropDataType.None;
pdwEffect = (uint)QueryDropEffect(grfKeyState);
dropDataType = ProcessSelectionDataObject(pDataObject, targetNode, true, (DropEffect)pdwEffect);
if (dropDataType == DropDataType.None) {
pdwEffect = (uint)DropEffect.None;
}
// 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 (!VsUtilities.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;
}
if (ItemsDraggedOrCutOrCopied != null)
{
ItemsDraggedOrCutOrCopied.Clear();
}
SourceDraggedOrCutOrCopied = CopyPasteDragSource.None;
return returnValue;
}
#endregion
#region override of IVsHierarchyDropDataSource2 methods
/// <summary>
/// Returns information about one or more of the items being dragged
/// </summary>
/// <param name="pdwOKEffects">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.</param>
/// <param name="ppDataObject">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.</param>
/// <param name="ppDropSource">Pointer to the IDropSource interface of the item being dragged.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code.</returns>
public 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;
}
SourceDraggedOrCutOrCopied = CopyPasteDragSource.Dragged;
pdwOKEffects = (uint)(DropEffect.Move | DropEffect.Copy);
ppDataObject = dataObject;
return VSConstants.S_OK;
}
/// <summary>
/// Notifies clients that the dragged item was dropped.
/// </summary>
/// <param name="fDropped">If true, then the dragged item was dropped on the target. If false, then the drop did not occur.</param>
/// <param name="dwEffects">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.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
public int OnDropNotify(int fDropped, uint dwEffects) {
if (SourceDraggedOrCutOrCopied == CopyPasteDragSource.None) {
return VSConstants.S_FALSE;
}
if (dwEffects == (uint)DropEffect.Move) {
foreach (var item in ItemsDraggedOrCutOrCopied) {
item.Remove(true);
}
}
ItemsDraggedOrCutOrCopied.Clear();
SourceDraggedOrCutOrCopied = CopyPasteDragSource.None;
return VSConstants.S_OK;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="o">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.</param>
/// <param name="dwEffect">Current state of the keyboard and the mouse modifier keys.</param>
/// <param name="fCancelDrop">If true, then the drop is cancelled by the source hierarchy. If false, then the drop can continue.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
public 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 (node.IsLinkFile) {
continue;
}
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
/// <summary>
/// Called after your cut/copied items has been pasted
/// </summary>
///<param name="wasCut">If true, then the IDataObject has been successfully pasted into a target hierarchy.
/// If false, then the cut or copy operation was cancelled.</param>
/// <param name="dropEffect">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</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
public virtual int OnPaste(int wasCut, uint dropEffect) {
if (SourceDraggedOrCutOrCopied == CopyPasteDragSource.None) {
return VSConstants.S_FALSE;
}
if (dropEffect == (uint)DropEffect.None) {
return OnClear(wasCut);
}
if (wasCut != 0 || dropEffect == (uint)DropEffect.Move) {
// If we just did a cut, 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.
CleanAndFlushClipboard();
foreach (HierarchyNode node in ItemsDraggedOrCutOrCopied) {
node.Remove(true);
}
ItemsDraggedOrCutOrCopied.Clear();
}
this.SourceDraggedOrCutOrCopied = CopyPasteDragSource.None;
return VSConstants.S_OK;
}
/// <summary>
/// Called when your cut/copied operation is canceled
/// </summary>
/// <param name="wasCut">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.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
public virtual int OnClear(int wasCut) {
if (SourceDraggedOrCutOrCopied == CopyPasteDragSource.None) {
return VSConstants.S_FALSE;
}
if (wasCut != 0) {
IVsUIHierarchyWindow w = UIHierarchyUtilities.GetUIHierarchyWindow(this.site, HierarchyNode.SolutionExplorer);
if (w != null) {
foreach (HierarchyNode node in ItemsDraggedOrCutOrCopied) {
node.ExpandItem(EXPANDFLAGS.EXPF_UnCutHighlightItem);
}
}
}
ItemsDraggedOrCutOrCopied.Clear();
this.SourceDraggedOrCutOrCopied = CopyPasteDragSource.None;
return VSConstants.S_OK;
}
#endregion
#region virtual methods
/// <summary>
/// Returns a dataobject from selected nodes
/// </summary>
/// <param name="cutHighlightItems">boolean that defines if the selected items must be cut</param>
/// <returns>data object for selected items</returns>
private DataObject PackageSelectionDataObject(bool cutHighlightItems) {
StringBuilder sb = new StringBuilder();
DataObject dataObject = null;
IList<HierarchyNode> selectedNodes = this.GetSelectedNodes();
if (selectedNodes != null) {
this.InstantiateItemsDraggedOrCutOrCopiedList();
// If there is a selection package the data
foreach (HierarchyNode node in selectedNodes) {
string selectionContent = node.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;
foreach (HierarchyNode node in this.ItemsDraggedOrCutOrCopied) {
node.ExpandItem(first ? EXPANDFLAGS.EXPF_CutHighlightItem : EXPANDFLAGS.EXPF_AddCutHighlightItem);
first = false;
}
}
return dataObject;
}
class ProjectReferenceFileAdder {
/// <summary>
/// This hierarchy which is having items added/moved
/// </summary>
private readonly ProjectNode Project;
/// <summary>
/// The node which we're adding/moving the items to
/// </summary>
private readonly HierarchyNode TargetNode;
/// <summary>
/// The references we're adding, using the format {Guid}|project|folderPath
/// </summary>
private readonly string[] ProjectReferences;
/// <summary>
/// True if this is the result of a mouse drop, false if this is the result of a paste
/// </summary>
private readonly bool MouseDropping;
/// <summary>
/// Move or Copy
/// </summary>
private readonly DropEffect DropEffect;
private bool? OverwriteAllItems;
public ProjectReferenceFileAdder(ProjectNode project, HierarchyNode targetNode, string[] projectReferences, bool mouseDropping, DropEffect dropEffect) {
VsUtilities.ArgumentNotNull("targetNode", targetNode);
Debug.Assert(project != null);
TargetNode = targetNode;
Project = project;
ProjectReferences = projectReferences;
MouseDropping = mouseDropping;
DropEffect = dropEffect;
}
public bool AddFiles() {
// Collect all of the additions.
List<Addition> additions = new List<Addition>();
List<string> folders = new List<string>();
// process folders first
foreach (string projectReference in ProjectReferences) {
if (projectReference == null) {
// bad projectref, bail out
return false;
}
if (CommonUtils.HasEndSeparator(projectReference)) {
var addition = CanAddFolderFromProjectReference(projectReference);
if (addition == null) {
return false;
}
additions.Add(addition);
FolderAddition folderAddition = addition as FolderAddition;
if (folderAddition != null) {
folders.Add(folderAddition.SourceFolder);
}
}
}
foreach (string projectReference in ProjectReferences) {
if (projectReference == null) {
// bad projectref, bail out
return false;
}
if (!CommonUtils.HasEndSeparator(projectReference)) {
var addition = CanAddFileFromProjectReference(projectReference, TargetNode.GetDragTargetHandlerNode().FullPathToChildren);
if (addition == null) {
return false;
}
FileAddition fileAddition = addition as FileAddition;
bool add = true;
if (fileAddition != null) {
foreach (var folder in folders) {
if (fileAddition.SourceMoniker.StartsWith(folder, StringComparison.OrdinalIgnoreCase)) {
// this will be moved/copied by the folder, it doesn't need another move/copy
add = false;
break;
}
}
}
if (add) {
additions.Add(addition);
}
}
}
foreach (var addition in additions) {
addition.DoAddition();
}
return true;
}
/// <summary>
/// Tests to see if we can add the folder to the project. Returns true if it's ok, false if it's not.
/// </summary>
/// <param name="folderToAdd">Project reference (from data object) using the format: {Guid}|project|folderPath</param>
/// <param name="targetNode">Node to add the new folder to</param>
private Addition CanAddFolderFromProjectReference(string folderToAdd) {
VsUtilities.ArgumentNotNullOrEmpty(folderToAdd, "folderToAdd");
var targetFolderNode = TargetNode.GetDragTargetHandlerNode();
string folder;
IVsHierarchy sourceHierarchy;
GetPathAndHierarchy(folderToAdd, out folder, out sourceHierarchy);
// Ensure we don't end up in an endless recursion
if (VsUtilities.IsSameComObject(Project, sourceHierarchy)) {
if (String.Equals(folder, targetFolderNode.FullPathToChildren, StringComparison.OrdinalIgnoreCase)) {
if (DropEffect == DropEffect.Move &&
IsBadMove(targetFolderNode.FullPathToChildren, folder, false)) {
return null;
}
}
if (targetFolderNode.FullPathToChildren.StartsWith(folder, StringComparison.OrdinalIgnoreCase) &&
!String.Equals(targetFolderNode.FullPathToChildren, folder, StringComparison.OrdinalIgnoreCase)) {
// dragging a folder into a child, that's not allowed
VsShellUtilities.ShowMessageBox(
Project.Site,
String.Format("Cannot move '{0}'. The destination folder is a subfolder of the source folder.", Path.GetFileName(CommonUtils.TrimEndSeparator(folder))),
null,
OLEMSGICON.OLEMSGICON_CRITICAL,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
return null;
}
}
var targetPath = Path.Combine(targetFolderNode.FullPathToChildren, Path.GetFileName(CommonUtils.TrimEndSeparator(folder)));
if (File.Exists(targetPath)) {
VsShellUtilities.ShowMessageBox(
Project.Site,
String.Format("Unable to add '{0}'. A file with that name already exists.", Path.GetFileName(CommonUtils.TrimEndSeparator(folder))),
null,
OLEMSGICON.OLEMSGICON_CRITICAL,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
return null;
}
if (Directory.Exists(targetPath)) {
if (DropEffect == DropEffect.Move) {
if (targetPath == folderToAdd) {
CannotMoveSameLocation(folderToAdd);
} else {
VsShellUtilities.ShowMessageBox(
Project.Site,
String.Format("Cannot move the folder '{0}'. A folder with that name already exists in the destination directory.", Path.GetFileName(CommonUtils.TrimEndSeparator(folder))),
null,
OLEMSGICON.OLEMSGICON_CRITICAL,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
return null;
}
var dialog = new OverwriteFileDialog(String.Format(
@"This folder already contains a folder called '{0}'
If the files in the existing folder have the same names as files in the
folder you are copying, do you want to replace the existing files?", Path.GetFileName(CommonUtils.TrimEndSeparator(folder))), false);
dialog.Owner = Application.Current.MainWindow;
var res = dialog.ShowDialog();
if (res != true) {
if (res == null) {
// cancel, abort the whole copy
return null;
} else {
// no, don't copy the folder
return NopAddition.Instance;
}
}
// otherwise yes, and we'll prompt about the files.
}
string targetFileName = Path.GetFileName(CommonUtils.TrimEndSeparator(folder));
if (VsUtilities.IsSameComObject(Project, sourceHierarchy) &&
String.Equals(targetFolderNode.FullPathToChildren, folder, StringComparison.OrdinalIgnoreCase)) {
// copying a folder onto its self, make a copy
targetFileName = GetCopyName(targetFolderNode.FullPathToChildren);
}
List<Addition> additions = new List<Addition>();
uint folderId;
if (ErrorHandler.Failed(sourceHierarchy.ParseCanonicalName(folder, out folderId))) {
// the folder may have been deleted between the copy & paste
ReportMissingItem(folder);
return null;
}
if (Path.Combine(targetFolderNode.FullPathToChildren, targetFileName).Length >= NativeMethods.MAX_FOLDER_PATH) {
VsShellUtilities.ShowMessageBox(
Project.Site,
"The folder name is too long.",
null,
OLEMSGICON.OLEMSGICON_CRITICAL,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
return null;
}
if (!WalkSourceProjectAndAdd(sourceHierarchy, folderId, targetFolderNode.FullPathToChildren, false, additions, targetFileName)) {
return null;
}
if (additions.Count == 1) {
return (FolderAddition)additions[0];
}
Debug.Assert(additions.Count == 0);
return null;
}
private void ReportMissingItem(string folder) {
VsShellUtilities.ShowMessageBox(
Project.Site,
String.Format("The source URL '{0}' could not be found.", Path.GetFileName(CommonUtils.TrimEndSeparator(folder))),
null,
OLEMSGICON.OLEMSGICON_CRITICAL,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="sourceHierarchy">Hierarchy to walk</param>
/// <param name="itemId">Item ID where to start walking the hierarchy</param>
/// <param name="targetNode">Node to start adding to</param>
/// <param name="addSibblings">Typically false on first call and true after that</param>
private bool WalkSourceProjectAndAdd(IVsHierarchy sourceHierarchy, uint itemId, string targetPath, bool addSiblings, List<Addition> additions, string name = null) {
Debug.Assert(sourceHierarchy != null);
if (itemId != VSConstants.VSITEMID_NIL) {
// Before we start the walk, add the current node
object variant = null;
// Calculate the corresponding path in our project
string source;
ErrorHandler.ThrowOnFailure(((IVsProject)sourceHierarchy).GetMkDocument(itemId, out source));
if (name == null) {
name = Path.GetFileName(CommonUtils.TrimEndSeparator(source));
}
Guid guidType;
ErrorHandler.ThrowOnFailure(sourceHierarchy.GetGuidProperty(itemId, (int)__VSHPROPID.VSHPROPID_TypeGuid, out guidType));
IVsSolution solution = Project.GetService(typeof(IVsSolution)) as IVsSolution;
if (solution != null) {
if (guidType == VSConstants.GUID_ItemType_PhysicalFile) {
string projRef;
ErrorHandler.ThrowOnFailure(solution.GetProjrefOfItem(sourceHierarchy, itemId, out projRef));
var addition = CanAddFileFromProjectReference(projRef, targetPath);
if (addition == null) {
// cancelled
return false;
}
additions.Add(addition);
}
}
// Start with child nodes (depth first)
if (guidType == VSConstants.GUID_ItemType_PhysicalFolder) {
variant = null;
ErrorHandler.ThrowOnFailure(sourceHierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_FirstVisibleChild, out variant));
uint currentItemID = (uint)(int)variant;
List<Addition> nestedAdditions = new List<Addition>();
string newPath = Path.Combine(targetPath, name);
if (!WalkSourceProjectAndAdd(sourceHierarchy, currentItemID, newPath, true, nestedAdditions)) {
// cancelled
return false;
}
additions.Add(new FolderAddition(Project, Path.Combine(targetPath, name), source, DropEffect, nestedAdditions.ToArray()));
}
if (addSiblings) {
// Then look at siblings
uint currentItemID = itemId;
while (currentItemID != VSConstants.VSITEMID_NIL) {
variant = null;
// http://mpfproj10.codeplex.com/workitem/11618 - pass currentItemID instead of itemId
ErrorHandler.ThrowOnFailure(sourceHierarchy.GetProperty(currentItemID, (int)__VSHPROPID.VSHPROPID_NextVisibleSibling, out variant));
currentItemID = (uint)(int)variant;
if (!WalkSourceProjectAndAdd(sourceHierarchy, currentItemID, targetPath, false, additions)) {
// cancelled
return false;
}
}
}
}
return true;
}
private static string GetCopyName(string existingFullPath) {
string newDir, name, extension;
if (CommonUtils.HasEndSeparator(existingFullPath)) {
name = Path.GetFileName(CommonUtils.TrimEndSeparator(existingFullPath));
extension = "";
} else {
extension = Path.GetExtension(existingFullPath);
name = Path.GetFileNameWithoutExtension(existingFullPath);
}
string folder = Path.GetDirectoryName(CommonUtils.TrimEndSeparator(existingFullPath));
int copyCount = 1;
do {
string newName = name + " - Copy";
if (copyCount != 1) {
newName += " (" + copyCount + ")";
}
newName += extension;
copyCount++;
newDir = Path.Combine(folder, newName);
} while (File.Exists(newDir) || Directory.Exists(newDir));
return newDir;
}
/// <summary>
/// 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.
/// </summary>
class FolderAddition : Addition {
private readonly ProjectNode Project;
private readonly string NewFolderPath;
public readonly string SourceFolder;
private readonly Addition[] Additions;
private readonly DropEffect DropEffect;
public FolderAddition(ProjectNode project, string newFolderPath, string sourceFolder, DropEffect dropEffect, Addition[] additions) {
Project = project;
NewFolderPath = newFolderPath;
SourceFolder = sourceFolder;
Additions = additions;
DropEffect = dropEffect;
}
public override void DoAddition() {
var newNode = Project.CreateFolderNodes(NewFolderPath);
foreach (var addition in Additions) {
addition.DoAddition();
}
var sourceFolder = Project.FindNodeByFullPath(SourceFolder);
if (sourceFolder != null && sourceFolder.IsNonMemberItem) {
// copying or moving an existing excluded folder, new folder
// is excluded too.
ErrorHandler.ThrowOnFailure(newNode.ExcludeFromProject());
}
if (DropEffect == DropEffect.Move) {
sourceFolder.Remove(true);
}
}
}
/// <summary>
/// Given the reference used for drag and drop returns the path to the item and it's
/// containing hierarchy.
/// </summary>
/// <param name="projectReference"></param>
/// <param name="path"></param>
/// <param name="sourceHierarchy"></param>
private void GetPathAndHierarchy(string projectReference, out string path, out IVsHierarchy sourceHierarchy) {
Guid projectInstanceGuid;
GetPathAndProjectId(projectReference, out projectInstanceGuid, out path);
// normalize the casing in case the project system gave us casing different from the file system
if (CommonUtils.HasEndSeparator(path)) {
try {
var trimmedPath = CommonUtils.TrimEndSeparator(path);
foreach (var dir in Directory.GetDirectories(Path.GetDirectoryName(trimmedPath), Path.GetFileName(trimmedPath))) {
if (String.Equals(dir, trimmedPath, StringComparison.OrdinalIgnoreCase)) {
path = dir + Path.DirectorySeparatorChar;
break;
}
}
} catch {
}
} else {
try {
foreach (var file in Directory.GetFiles(Path.GetDirectoryName(path))) {
if (String.Equals(file, path, StringComparison.OrdinalIgnoreCase)) {
path = file;
break;
}
}
} catch {
}
}
// Retrieve the project from which the items are being copied
IVsSolution solution = (IVsSolution)Project.GetService(typeof(SVsSolution));
ErrorHandler.ThrowOnFailure(solution.GetProjectOfGuid(ref projectInstanceGuid, out sourceHierarchy));
}
private static void GetPathAndProjectId(string projectReference, out Guid projectInstanceGuid, out string folder) {
// Split the reference in its 3 parts
int index1 = Guid.Empty.ToString("B").Length;
if (index1 + 1 >= projectReference.Length)
throw new ArgumentOutOfRangeException("folderToAdd");
// Get the Guid
string guidString = projectReference.Substring(1, index1 - 2);
projectInstanceGuid = new Guid(guidString);
// Get the project path
int index2 = projectReference.IndexOf('|', index1 + 1);
if (index2 < 0 || index2 + 1 >= projectReference.Length)
throw new ArgumentOutOfRangeException("folderToAdd");
// Finally get the source path
folder = projectReference.Substring(index2 + 1);
}
/// <summary>
/// Adds an item from a project refererence to target node.
/// </summary>
/// <param name="projectRef"></param>
/// <param name="targetNode"></param>
private Addition CanAddFileFromProjectReference(string projectRef, string targetFolder, bool fromFolder = false) {
VsUtilities.ArgumentNotNullOrEmpty("projectRef", projectRef);
IVsSolution solution = Project.GetService(typeof(IVsSolution)) as IVsSolution;
VsUtilities.CheckNotNull(solution);
uint itemidLoc;
IVsHierarchy hierarchy;
string str;
VSUPDATEPROJREFREASON[] reason = new VSUPDATEPROJREFREASON[1];
if (ErrorHandler.Failed(solution.GetItemOfProjref(projectRef, out hierarchy, out itemidLoc, out str, reason))) {
// the file may have been deleted between the copy & paste
string path;
Guid projectGuid;
GetPathAndProjectId(projectRef, out projectGuid, out path);
ReportMissingItem(path);
return null;
}
VsUtilities.CheckNotNull(hierarchy);
// This will throw invalid cast exception if the hierrachy is not a project.
IVsProject project = (IVsProject)hierarchy;
string moniker;
ErrorHandler.ThrowOnFailure(project.GetMkDocument(itemidLoc, out moniker));
if (DropEffect == DropEffect.Move && IsBadMove(targetFolder, moniker, true)) {
return null;
}
if (!File.Exists(moniker)) {
VsShellUtilities.ShowMessageBox(
Project.Site,
String.Format("The item '{0}' does not exist in the project directory. It may have been moved, renamed or deleted.", Path.GetFileName(moniker)),
null,
OLEMSGICON.OLEMSGICON_CRITICAL,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
return null;
}
string newPath = Path.Combine(targetFolder, Path.GetFileName(moniker));
var existingChild = Project.FindNodeByFullPath(moniker);
if (existingChild != null && existingChild.IsLinkFile) {
if (DropEffect == DropEffect.Move) {
// moving a link file, just update it's location in the hierarchy
return new ReparentLinkedFileAddition(Project, targetFolder, moniker);
} else {
//
VsShellUtilities.ShowMessageBox(
Project.Site,
String.Format("Cannot copy linked files within the same project. You cannot have more than one link to the same file in a project."),
null,
OLEMSGICON.OLEMSGICON_CRITICAL,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
return null;
}
} else if (File.Exists(newPath) && CommonUtils.IsSamePath(newPath, moniker)) {
newPath = GetCopyName(newPath);
}
bool ok = false;
if (DropEffect == DropEffect.Move && VsUtilities.IsSameComObject(project, Project)) {
ok = Project.Tracker.CanRenameItem(moniker, newPath, VSRENAMEFILEFLAGS.VSRENAMEFILEFLAGS_NoFlags);
} else {
ok = Project.Tracker.CanAddItems(
new[] { newPath },
new VSQUERYADDFILEFLAGS[] { VSQUERYADDFILEFLAGS.VSQUERYADDFILEFLAGS_NoFlags });
}
if (ok) {
if (File.Exists(newPath)) {
if (DropEffect == DropEffect.Move &&
VsUtilities.IsSameComObject(project, Project) &&
Project.FindNodeByFullPath(newPath) != null) {
// if we're overwriting an item, we're moving it, make sure that's ok.
// OverwriteFileAddition will handle the remove from the hierarchy
if (!Project.Tracker.CanRemoveItems(new[] { newPath }, new[] { VSQUERYREMOVEFILEFLAGS.VSQUERYREMOVEFILEFLAGS_NoFlags })) {
return null;
}
}
bool? overwrite = OverwriteAllItems;
if (overwrite == null) {
var dialog = new OverwriteFileDialog(String.Format("A file with the name '{0}' already exists. Do you want to replace it?", Path.GetFileName(moniker)), true);
dialog.Owner = Application.Current.MainWindow;
bool? dialogResult = dialog.ShowDialog();
if (dialogResult != null && !dialogResult.Value) {
// user cancelled
return null;
}
overwrite = dialog.ShouldOverwrite;
if (dialog.AllItems) {
OverwriteAllItems = overwrite;
}
}
if (overwrite.Value) {
return new OverwriteFileAddition(Project, targetFolder, DropEffect, moniker, Path.GetFileName(newPath), project);
} else {
return NopAddition.Instance;
}
}
if (newPath.Length >= NativeMethods.MAX_PATH) {
VsShellUtilities.ShowMessageBox(
Project.Site,
"The filename is too long.",
null,
OLEMSGICON.OLEMSGICON_CRITICAL,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
return null;
}
return new FileAddition(Project, targetFolder, DropEffect, moniker, Path.GetFileName(newPath), project);
}
return null;
}
private bool IsBadMove(string targetFolder, string moniker, bool file) {
if (TargetNode.GetMkDocument() == moniker) {
// we are moving the file onto it's self. If it's a single file via mouse
// we'll ignore it. If it's multiple files, or a cut and paste, then we'll
// report the error.
if (ProjectReferences.Length > 1 || !MouseDropping) {
CannotMoveSameLocation(moniker);
}
return true;
}
if ((file || !MouseDropping) &&
Directory.Exists(targetFolder) &&
CommonUtils.IsSameDirectory(Path.GetDirectoryName(moniker), targetFolder)) {
// we're moving a file into it's own folder, report an error.
CannotMoveSameLocation(moniker);
return true;
}
return false;
}
private void CannotMoveSameLocation(string moniker) {
VsShellUtilities.ShowMessageBox(
Project.Site,
String.Format("Cannot move '{0}'. The destination folder is the same as the source folder.", Path.GetFileName(CommonUtils.TrimEndSeparator(moniker))),
null,
OLEMSGICON.OLEMSGICON_CRITICAL,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
private bool IsOurProject(IVsProject project) {
string projectDoc;
project.GetMkDocument((uint)VSConstants.VSITEMID.Root, out projectDoc);
return projectDoc == Project.Url;
}
abstract class Addition {
public abstract void DoAddition();
}
class NopAddition : Addition {
public static NopAddition Instance = new NopAddition();
public override void DoAddition() {
}
}
class ReparentLinkedFileAddition : Addition {
private readonly ProjectNode Project;
private readonly string TargetFolder;
private readonly string Moniker;
public ReparentLinkedFileAddition(ProjectNode project, string targetFolder, string moniker) {
Project = project;
TargetFolder = targetFolder;
Moniker = moniker;
}
public override void DoAddition() {
var existing = Project.FindNodeByFullPath(Moniker);
existing.Parent.RemoveChild(existing);
Project.OnItemDeleted(existing);
existing.ID = Project.ItemIdMap.Add(existing);
var newParent = TargetFolder == Project.ProjectHome ? Project : Project.FindNodeByFullPath(TargetFolder);
newParent.AddChild(existing);
Project.ItemsDraggedOrCutOrCopied.Remove(existing); // we don't need to remove the file after Paste
var link = existing.ItemNode.GetMetadata(ProjectFileConstants.Link);
if (link != null) {
// update the link to the new location within solution explorer
existing.ItemNode.SetMetadata(
ProjectFileConstants.Link,
Path.Combine(
CommonUtils.GetRelativeDirectoryPath(
Project.ProjectHome,
TargetFolder
),
Path.GetFileName(Moniker)
)
);
}
}
}
class FileAddition : Addition {
public readonly ProjectNode Project;
public readonly string TargetFolder;
public readonly DropEffect DropEffect;
public readonly string SourceMoniker;
public readonly IVsProject SourceHierarchy;
public readonly string NewFileName;
public FileAddition(ProjectNode project, string targetFolder, DropEffect dropEffect, string sourceMoniker, string newFileName, IVsProject sourceHierarchy) {
Project = project;
TargetFolder = targetFolder;
DropEffect = dropEffect;
SourceMoniker = sourceMoniker;
SourceHierarchy = sourceHierarchy;
NewFileName = newFileName;
}
public override void DoAddition() {
string newPath = Path.Combine(TargetFolder, NewFileName);
if (DropEffect == DropEffect.Move && VsUtilities.IsSameComObject(Project, SourceHierarchy)) {
// we are doing a move, we need to remove the old item, and add the new.
var fileNode = Project.FindNodeByFullPath(SourceMoniker);
Debug.Assert(fileNode is FileNode);
FileNode file = fileNode as FileNode;
file.RenameInStorage(fileNode.Url, newPath);
file.RenameFileNode(fileNode.Url, newPath);
Project.Tracker.OnItemRenamed(SourceMoniker, newPath, VSRENAMEFILEFLAGS.VSRENAMEFILEFLAGS_NoFlags);
Project.ItemsDraggedOrCutOrCopied.Remove(fileNode); // we don't need to remove the file after Paste
} else {
// we are copying and adding a new file node
File.Copy(SourceMoniker, newPath, true);
var existing = Project.FindNodeByFullPath(newPath);
if (existing == null) {
var fileNode = Project.CreateFileNode(newPath);
if (String.Equals(TargetFolder, Project.FullPathToChildren, StringComparison.OrdinalIgnoreCase)) {
Project.AddChild(fileNode);
} else {
var targetFolder = Project.CreateFolderNodes(TargetFolder);
if (targetFolder.IsNonMemberItem) {
// dropping/pasting folder into non-member folder, non member folder
// should get included into the project.
ErrorHandler.ThrowOnFailure(targetFolder.IncludeInProject(false));
}
targetFolder.AddChild(fileNode);
}
}
}
Project.SetProjectFileDirty(true);
}
}
class OverwriteFileAddition : FileAddition {
public OverwriteFileAddition(ProjectNode project, string targetFolder, DropEffect dropEffect, string sourceMoniker, string newFileName, IVsProject sourceHierarchy)
: base(project, targetFolder, dropEffect, sourceMoniker, newFileName, sourceHierarchy) {
}
public override void DoAddition() {
if (DropEffect == DropEffect.Move) {
// File.Move won't overwrite, do it now.
File.Delete(Path.Combine(TargetFolder, Path.GetFileName(NewFileName)));
HierarchyNode existingNode;
if (VsUtilities.IsSameComObject(SourceHierarchy, Project) &&
(existingNode = Project.FindNodeByFullPath(Path.Combine(TargetFolder, NewFileName))) != null) {
// remove the existing item from the hierarchy, base.DoAddition will add a new one
existingNode.Remove(true);
}
}
base.DoAddition();
}
}
}
/// <summary>
/// Add an existing item (file/folder) to the project if it already exist in our storage.
/// </summary>
/// <param name="parentNode">Node to that this item to</param>
/// <param name="name">Name of the item being added</param>
/// <param name="targetPath">Path of the item being added</param>
/// <returns>Node that was added</returns>
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.FindNodeByFullPath(targetPath);
if (newNode == null)
throw new Exception();
} else if (Directory.Exists(targetPath)) {
newNode = this.CreateFolderNodes(targetPath);
}
return newNode;
}
#endregion
#region non-virtual methods
/// <summary>
/// Handle the Cut operation to the clipboard
/// </summary>
public int CutToClipboard() {
int returnValue = (int)OleConstants.OLECMDERR_E_NOTSUPPORTED;
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 = CopyPasteDragSource.Cut;
// 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));
}
return returnValue;
}
/// <summary>
/// Handle the Copy operation to the clipboard
/// </summary>
public int CopyToClipboard() {
int returnValue = (int)OleConstants.OLECMDERR_E_NOTSUPPORTED;
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 = CopyPasteDragSource.Copied;
// 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));
}
return returnValue;
}
/// <summary>
/// Handle the Paste operation to a targetNode
/// </summary>
public 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;
ErrorHandler.ThrowOnFailure(UnsafeNativeMethods.OleGetClipboard(out dataObject));
if (dataObject == null) {
return VSConstants.E_UNEXPECTED;
}
DropEffect dropEffect = DropEffect.None;
DropDataType dropDataType = DropDataType.None;
try {
dropEffect = SourceDraggedOrCutOrCopied == CopyPasteDragSource.Cut ? DropEffect.Move : DropEffect.Copy;
dropDataType = this.ProcessSelectionDataObject(dataObject, targetNode, false, dropEffect);
if (dropDataType == DropDataType.None) {
dropEffect = DropEffect.None;
}
} 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)(SourceDraggedOrCutOrCopied == CopyPasteDragSource.None ? DropEffect.Move : dropEffect));
}
} catch (COMException e) {
Trace.WriteLine("Exception : " + e.Message);
returnValue = e.ErrorCode;
}
return returnValue;
}
/// <summary>
/// Determines if the paste command should be allowed.
/// </summary>
/// <returns></returns>
public 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;
}
}
/// <summary>
/// Register/Unregister for Clipboard events for the UiHierarchyWindow (solution explorer)
/// </summary>
/// <param name="register">true for register, false for unregister</param>
public 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, 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;
}
}
/// <summary>
/// Process dataobject from Drag/Drop/Cut/Copy/Paste operation
///
/// drop indicates if it is a drag/drop or a cut/copy/paste.
/// </summary>
/// <remarks>The targetNode is set if the method is called from a drop operation, otherwise it is null</remarks>
public DropDataType ProcessSelectionDataObject(IOleDataObject dataObject, HierarchyNode targetNode, bool drop, DropEffect dropEffect) {
Debug.Assert(targetNode != null);
DropDataType dropDataType = DropDataType.None;
bool isWindowsFormat = false;
// Try to get it as a directory based project.
List<string> 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;
// For directory based projects the content of the clipboard is a double-NULL terminated list of Projref strings.
if (isWindowsFormat) {
DropFilesOrFolders(filesDroppedAsArray, node);
return dropDataType;
} else {
if (AddFilesFromProjectReferences(node, filesDroppedAsArray, drop, dropEffect)) {
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;
}
public void DropFilesOrFolders(string[] filesDropped, HierarchyNode ontoNode) {
var waitDialog = (IVsThreadedWaitDialog)Site.GetService(typeof(SVsThreadedWaitDialog));
int waitResult = waitDialog.StartWaitDialog(
"Adding files and folders...",
"Adding files to your project, this may take several seconds...",
null,
0,
null,
null
);
try {
ontoNode = ontoNode.GetDragTargetHandlerNode();
string nodePath = ontoNode.FullPathToChildren;
bool droppingExistingDirectory = true;
foreach (var droppedFile in filesDropped) {
if (!Directory.Exists(droppedFile) ||
!String.Equals(Path.GetDirectoryName(droppedFile), nodePath, StringComparison.OrdinalIgnoreCase)) {
droppingExistingDirectory = false;
break;
}
}
if (droppingExistingDirectory) {
// we're dragging a directory/directories that already exist
// into the location where they exist, we can do this via a fast path,
// and pop up a nice progress bar.
AddExistingDirectories(ontoNode, filesDropped);
} else {
foreach (var droppedFile in filesDropped) {
if (Directory.Exists(droppedFile) &&
CommonUtils.IsSubpathOf(droppedFile, nodePath)) {
int cancelled = 0;
waitDialog.EndWaitDialog(ref cancelled);
waitResult = VSConstants.E_FAIL; // don't end twice
VsShellUtilities.ShowMessageBox(
Site,
String.Format("Cannot add folder '{0}' as a child or decedent of self.", Path.GetFileName(CommonUtils.TrimEndSeparator(droppedFile))),
null,
OLEMSGICON.OLEMSGICON_CRITICAL,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
return;
}
}
// This is the code path when source is windows explorer
VSADDRESULT[] vsaddresults = new VSADDRESULT[1];
vsaddresults[0] = VSADDRESULT.ADDRESULT_Failure;
int addResult = AddItem(ontoNode.ID, VSADDITEMOPERATION.VSADDITEMOP_OPENFILE, null, (uint)filesDropped.Length, filesDropped, 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);
}
}
} finally {
if (ErrorHandler.Succeeded(waitResult)) {
int cancelled = 0;
waitDialog.EndWaitDialog(ref cancelled);
}
}
}
public void AddExistingDirectories(HierarchyNode node, string[] filesDropped) {
List<KeyValuePair<HierarchyNode, HierarchyNode>> addedItems = new List<KeyValuePair<HierarchyNode, HierarchyNode>>();
var oldTriggerFlag = this.EventTriggeringFlag;
EventTriggeringFlag |= ProjectNode.EventTriggering.DoNotTriggerHierarchyEvents;
try {
foreach (var dir in filesDropped) {
AddExistingDirectory(GetOrAddDirectory(node, addedItems, dir), dir, addedItems);
}
} finally {
EventTriggeringFlag = oldTriggerFlag;
}
if (addedItems.Count > 0) {
foreach (var item in addedItems) {
OnItemAdded(item.Key, item.Value);
this.tracker.OnItemAdded(item.Value.Url, VSADDFILEFLAGS.VSADDFILEFLAGS_NoFlags);
}
OnInvalidateItems(node);
SetProjectFileDirty(true);
}
}
private void AddExistingDirectory(HierarchyNode node, string path, List<KeyValuePair<HierarchyNode, HierarchyNode>> addedItems) {
foreach (var dir in Directory.GetDirectories(path)) {
var existingDir = GetOrAddDirectory(node, addedItems, dir);
AddExistingDirectory(existingDir, dir, addedItems);
}
foreach (var file in Directory.GetFiles(path)) {
var existingFile = node.FindImmediateChildByName(Path.GetFileName(file));
if (existingFile == null) {
existingFile = CreateFileNode(file);
addedItems.Add(new KeyValuePair<HierarchyNode, HierarchyNode>(node, existingFile));
node.AddChild(existingFile);
}
}
}
private HierarchyNode GetOrAddDirectory(HierarchyNode node, List<KeyValuePair<HierarchyNode, HierarchyNode>> addedItems, string dir) {
var existingDir = node.FindImmediateChildByName(Path.GetFileName(dir));
if (existingDir == null) {
existingDir = CreateFolderNode(dir);
addedItems.Add(new KeyValuePair<HierarchyNode, HierarchyNode>(node, existingDir));
node.AddChild(existingDir);
}
return existingDir;
}
/// <summary>
/// Get the dropdatatype from the dataobject
/// </summary>
/// <param name="pDataObject">The dataobject to be analysed for its format</param>
/// <returns>dropdatatype or none if dataobject does not contain known format</returns>
public 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;
}
/// <summary>
/// Returns the drop effect.
/// </summary>
/// <remarks>
/// // 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)
/// </remarks>
public DropEffect QueryDropEffect(uint grfKeyState) {
//Validate the dropdatatype
if ((_dropType != DropDataType.Shell) && (_dropType != DropDataType.VsRef) && (_dropType != 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 (SourceDraggedOrCutOrCopied == CopyPasteDragSource.Cut ||
(ItemsDraggedOrCutOrCopied != null &&
ItemsDraggedOrCutOrCopied.Count > 0)) {
return DropEffect.Move;
} else {
return DropEffect.Copy;
}
}
/// <summary>
/// Moves files from one part of our project to another.
/// </summary>
/// <param name="targetNode">the targetHandler node</param>
/// <param name="projectReferences">List of projectref string</param>
/// <returns>true if succeeded</returns>
public bool AddFilesFromProjectReferences(HierarchyNode targetNode, string[] projectReferences, bool mouseDropping, DropEffect dropEffect) {
//Validate input
VsUtilities.ArgumentNotNull("projectReferences", projectReferences);
VsUtilities.CheckNotNull(targetNode);
if (!QueryEditProjectFile(false)) {
throw Marshal.GetExceptionForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED);
}
return new ProjectReferenceFileAdder(this, targetNode, projectReferences, mouseDropping, dropEffect).AddFiles();
}
#endregion
#region private helper methods
/// <summary>
/// Empties all the data structures added to the clipboard and flushes the clipboard.
/// </summary>
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) && CommonUtils.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
}
}