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.
1636 lines
78 KiB
C#
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
|
|
}
|
|
}
|