mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-08 11:38:21 +02:00
Copy/cut/paste support for workspace files and folders
This commit is contained in:
parent
0907bd1f52
commit
05c2e1587c
4 changed files with 529 additions and 207 deletions
|
@ -792,9 +792,23 @@ namespace Beefy
|
|||
BFApp_SetCursor((int32)cursor);
|
||||
}
|
||||
|
||||
public virtual void* GetClipboardData(String format, out int32 size)
|
||||
public virtual void* GetClipboardData(String format, out int32 size, int waitTime = 500)
|
||||
{
|
||||
return BFApp_GetClipboardData(format, out size);
|
||||
Stopwatch sw = null;
|
||||
repeat
|
||||
{
|
||||
if (sw != null)
|
||||
Thread.Sleep(1);
|
||||
void* result = BFApp_GetClipboardData(format, out size);
|
||||
if (size != -1)
|
||||
return result;
|
||||
if (waitTime == 0)
|
||||
return null;
|
||||
if (sw == null)
|
||||
sw = scope:: .()..Start();
|
||||
}
|
||||
while (waitTime < sw.ElapsedMilliseconds);
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void ReleaseClipboardData(void* ptr)
|
||||
|
@ -802,9 +816,9 @@ namespace Beefy
|
|||
BFApp_ReleaseClipboardData(ptr);
|
||||
}
|
||||
|
||||
public virtual bool GetClipboardText(String outStr)
|
||||
public virtual bool GetClipboardText(String outStr, int waitTime = 500)
|
||||
{
|
||||
return GetClipboardTextData("text", outStr);
|
||||
return GetClipboardTextData("text", outStr, waitTime);
|
||||
}
|
||||
|
||||
public virtual bool GetClipboardText(String outStr, String extra)
|
||||
|
@ -813,10 +827,10 @@ namespace Beefy
|
|||
return GetClipboardTextData("text", outStr);
|
||||
}
|
||||
|
||||
public bool GetClipboardTextData(String format, String outStr)
|
||||
public bool GetClipboardTextData(String format, String outStr, int waitTime = 500)
|
||||
{
|
||||
int32 aSize;
|
||||
void* clipboardData = GetClipboardData(format, out aSize);
|
||||
void* clipboardData = GetClipboardData(format, out aSize, waitTime);
|
||||
if (clipboardData == null)
|
||||
return false;
|
||||
|
||||
|
|
|
@ -1867,6 +1867,11 @@ void* WinBFApp::GetClipboardData(const StringImpl& format, int* size)
|
|||
CloseClipboard();
|
||||
return (void*)gClipboardData.c_str();
|
||||
}
|
||||
else
|
||||
{
|
||||
*size = -1;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
*size = 0;
|
||||
|
|
|
@ -304,6 +304,7 @@ namespace IDE
|
|||
public Color mWorkspaceFailedText = 0xFFE04040;
|
||||
public Color mWorkspaceManualIncludeText = 0xFFE0E0FF;
|
||||
public Color mWorkspaceIgnoredText = 0xFF909090;
|
||||
public Color mWorkspaceCutText = 0xFFC0B0B0;
|
||||
|
||||
public Color mCode = 0xFFFFFFFF;
|
||||
public Color mKeyword = 0xFFE1AE9A;
|
||||
|
|
|
@ -61,6 +61,17 @@ namespace IDE.ui
|
|||
else if (projectItem.mIncludeKind == .Ignore)
|
||||
color = Color.Mult(color, gApp.mSettings.mUISettings.mColors.mWorkspaceIgnoredText);
|
||||
|
||||
if (let projectFileItem = projectItem as ProjectFileItem)
|
||||
{
|
||||
if (projectPanel.mClipboardCutQueued != null)
|
||||
{
|
||||
var path = projectFileItem.mProject.GetProjectFullPath(projectFileItem.mPath, .. scope .());
|
||||
IDEUtils.MakeComparableFilePath(path);
|
||||
if (projectPanel.mClipboardCutQueued.Contains(path))
|
||||
color = Color.Mult(color, gApp.mSettings.mUISettings.mColors.mWorkspaceCutText);
|
||||
}
|
||||
}
|
||||
|
||||
if (let projectSource = projectItem as ProjectSource)
|
||||
{
|
||||
if (projectSource.mLoadFailed)
|
||||
|
@ -146,6 +157,7 @@ namespace IDE.ui
|
|||
public bool mShowIgnored = true;
|
||||
public bool mSortDirty;
|
||||
public bool mWantsRehup;
|
||||
public HashSet<String> mClipboardCutQueued ~ DeleteContainerAndItems!(_);
|
||||
|
||||
public this()
|
||||
{
|
||||
|
@ -2050,6 +2062,307 @@ namespace IDE.ui
|
|||
}
|
||||
}
|
||||
|
||||
void CopyToClipboard()
|
||||
{
|
||||
String clipData = scope .();
|
||||
mListView.GetRoot().WithSelectedItems(scope (selectedItem) =>
|
||||
{
|
||||
if (mListViewToProjectMap.GetValue(selectedItem) case .Ok(var sourceProjectItem))
|
||||
{
|
||||
String path = scope .();
|
||||
if (var projectFileItem = sourceProjectItem as ProjectFileItem)
|
||||
{
|
||||
sourceProjectItem.mProject.GetProjectFullPath(projectFileItem.mPath, path);
|
||||
path.Replace('\\', '/');
|
||||
|
||||
if (!clipData.IsEmpty)
|
||||
clipData.Append("\n");
|
||||
clipData.Append("file:///");
|
||||
IDEUtils.URLEncode(path, clipData);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!clipData.IsEmpty)
|
||||
gApp.SetClipboardData("code/file-list", clipData.Ptr, (.)clipData.Length, true);
|
||||
}
|
||||
|
||||
void CutToClipboard()
|
||||
{
|
||||
DeleteContainerAndItems!(mClipboardCutQueued);
|
||||
mClipboardCutQueued = null;
|
||||
|
||||
CopyToClipboard();
|
||||
mClipboardCutQueued = new .();
|
||||
ValidateCutClipboard();
|
||||
}
|
||||
|
||||
void ValidateCutClipboard()
|
||||
{
|
||||
if (mClipboardCutQueued == null)
|
||||
return;
|
||||
|
||||
void* data = gApp.GetClipboardData("code/file-list", var size, 0);
|
||||
if (size == -1)
|
||||
return;
|
||||
|
||||
ClearAndDeleteItems!(mClipboardCutQueued);
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
StringView sv = .((.)data, size);
|
||||
for (var line in sv.Split('\n'))
|
||||
{
|
||||
var uri = IDEUtils.URLDecode(line, .. scope .());
|
||||
if (uri.StartsWith("file:///"))
|
||||
{
|
||||
var srcPath = scope String()..Append(uri.Substring("file:///".Length));
|
||||
IDEUtils.MakeComparableFilePath(srcPath);
|
||||
if (mClipboardCutQueued.TryAddAlt(srcPath, var entryPtr))
|
||||
*entryPtr = new String(srcPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mClipboardCutQueued.IsEmpty)
|
||||
DeleteAndNullify!(mClipboardCutQueued);
|
||||
}
|
||||
|
||||
void PasteFromClipboard()
|
||||
{
|
||||
ValidateCutClipboard();
|
||||
|
||||
var projectItem = GetSelectedProjectItem();
|
||||
var projectFolder = projectItem as ProjectFolder;
|
||||
if (projectFolder == null)
|
||||
projectFolder = projectItem.mParentFolder;
|
||||
if (projectFolder == null)
|
||||
return;
|
||||
|
||||
var folderPath = projectFolder.GetFullImportPath(.. scope .());
|
||||
|
||||
void* data = gApp.GetClipboardData("code/file-list", var size);
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
bool isCut = mClipboardCutQueued != null;
|
||||
Dictionary<String, SourceViewPanel> sourceViewPanelMap = null;
|
||||
List<(SourceViewPanel sourceViewPanel, String fromPath, String toPath)> moveList = null;
|
||||
|
||||
HashSet<String> foundDirs = scope .();
|
||||
|
||||
if (isCut)
|
||||
{
|
||||
sourceViewPanelMap = scope:: .();
|
||||
gApp.WithSourceViewPanels(scope (sourceViewPanel) =>
|
||||
{
|
||||
if (sourceViewPanel.mFilePath === null)
|
||||
return;
|
||||
if (sourceViewPanel.mProjectSource == null)
|
||||
return;
|
||||
|
||||
var path = scope String()..Append(sourceViewPanel.mFilePath);
|
||||
IDEUtils.MakeComparableFilePath(path);
|
||||
if (sourceViewPanelMap.TryAdd(path, var keyPtr, var valuePtr))
|
||||
{
|
||||
*keyPtr = new String(path);
|
||||
*valuePtr = sourceViewPanel;
|
||||
}
|
||||
});
|
||||
|
||||
moveList = scope:: .();
|
||||
}
|
||||
|
||||
defer
|
||||
{
|
||||
DeleteContainerAndItems!(mClipboardCutQueued);
|
||||
mClipboardCutQueued = null;
|
||||
|
||||
if (sourceViewPanelMap != null)
|
||||
{
|
||||
for (var key in sourceViewPanelMap.Keys)
|
||||
delete key;
|
||||
}
|
||||
|
||||
if (moveList != null)
|
||||
{
|
||||
for (var val in moveList)
|
||||
{
|
||||
delete val.fromPath;
|
||||
delete val.toPath;
|
||||
}
|
||||
}
|
||||
|
||||
ClearAndDeleteItems!(foundDirs);
|
||||
}
|
||||
|
||||
void QueueDirectoryMove(StringView fromDir, StringView toDir)
|
||||
{
|
||||
var searchStr = scope String();
|
||||
searchStr.Append(fromDir);
|
||||
searchStr.Append("/*");
|
||||
for (var dirEntry in Directory.Enumerate(searchStr, .Directories | .Files))
|
||||
{
|
||||
var fromChildPath = dirEntry.GetFilePath(.. scope .());
|
||||
|
||||
String toChildPath = scope String()..Append(toDir);
|
||||
toChildPath.Append(Path.DirectorySeparatorChar);
|
||||
dirEntry.GetFileName(toChildPath);
|
||||
|
||||
if (dirEntry.IsDirectory)
|
||||
{
|
||||
QueueDirectoryMove(fromChildPath, toChildPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
var cmpPath = IDEUtils.MakeComparableFilePath(.. scope String()..Append(fromChildPath));
|
||||
if (sourceViewPanelMap.TryGet(cmpPath, var matchKey, var sourceViewPanel))
|
||||
{
|
||||
moveList.Add((sourceViewPanel, new String(fromChildPath), new String(toChildPath)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<void> CopyDirectory(StringView fromDir, StringView toDir)
|
||||
{
|
||||
if (Directory.CreateDirectory(toDir) case .Err)
|
||||
{
|
||||
gApp.Fail(scope $"Failed to create directory '{toDir}'");
|
||||
return .Err;
|
||||
}
|
||||
|
||||
var searchStr = scope String();
|
||||
searchStr.Append(fromDir);
|
||||
searchStr.Append("/*");
|
||||
for (var dirEntry in Directory.Enumerate(searchStr, .Directories | .Files))
|
||||
{
|
||||
var fromChildPath = dirEntry.GetFilePath(.. scope .());
|
||||
|
||||
String toChildPath = scope String()..Append(toDir);
|
||||
toChildPath.Append("/");
|
||||
dirEntry.GetFileName(toChildPath);
|
||||
|
||||
if (dirEntry.IsDirectory)
|
||||
{
|
||||
CopyDirectory(fromChildPath, toChildPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (File.Copy(fromChildPath, toChildPath) case .Err)
|
||||
{
|
||||
gApp.Fail(scope $"Failed to copy '{fromChildPath}' to '{toChildPath}'");
|
||||
return .Err;
|
||||
}
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
StringView sv = .((.)data, size);
|
||||
SrcLoop: for (var line in sv.Split('\n'))
|
||||
{
|
||||
var uri = IDEUtils.URLDecode(line, .. scope .());
|
||||
if (uri.StartsWith("file:///"))
|
||||
{
|
||||
var srcPath = scope String()..Append(uri.Substring("file:///".Length));
|
||||
|
||||
for (int i < 100)
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(srcPath, .. scope .());
|
||||
var destPath = scope String();
|
||||
destPath.Append(folderPath);
|
||||
destPath.Append("/");
|
||||
destPath.Append(fileName);
|
||||
if ((i > 0) && (!fileName.Contains(" - Copy")))
|
||||
destPath.Append(" - Copy");
|
||||
if (i > 1)
|
||||
destPath.AppendF($" ({i})");
|
||||
Path.GetExtension(srcPath, destPath);
|
||||
|
||||
IDEUtils.FixFilePath(srcPath);
|
||||
IDEUtils.FixFilePath(destPath);
|
||||
|
||||
if ((isCut) && (Path.Equals(srcPath, destPath)))
|
||||
break;
|
||||
|
||||
if (File.Exists(destPath))
|
||||
continue;
|
||||
if (Directory.Exists(destPath))
|
||||
continue;
|
||||
|
||||
if (Directory.Exists(srcPath))
|
||||
{
|
||||
if (foundDirs.TryAdd(srcPath, var entryPtr))
|
||||
*entryPtr = new String(srcPath);
|
||||
|
||||
if (isCut)
|
||||
{
|
||||
QueueDirectoryMove(srcPath, destPath);
|
||||
|
||||
if (Directory.Move(srcPath, destPath) case .Err)
|
||||
{
|
||||
gApp.Fail(scope $"Failed to move '{srcPath}' to '{destPath}'");
|
||||
return;
|
||||
}
|
||||
|
||||
for (var val in moveList)
|
||||
{
|
||||
gApp.FileRenamed(val.sourceViewPanel.mProjectSource, val.fromPath, val.toPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CopyDirectory(srcPath, destPath) case .Err)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var checkPath = scope String()..Append(srcPath);
|
||||
while (true)
|
||||
{
|
||||
String checkDir = scope .();
|
||||
if (Path.GetDirectoryPath(checkPath, checkDir) case .Err)
|
||||
break;
|
||||
if (foundDirs.Contains(checkDir))
|
||||
{
|
||||
// Already handled
|
||||
continue SrcLoop;
|
||||
}
|
||||
checkPath.Set(checkDir);
|
||||
}
|
||||
|
||||
if (isCut)
|
||||
{
|
||||
if (File.Move(srcPath, destPath) case .Err)
|
||||
{
|
||||
gApp.Fail(scope $"Failed to move '{srcPath}' to '{destPath}'");
|
||||
return;
|
||||
}
|
||||
|
||||
var cmpPath = IDEUtils.MakeComparableFilePath(.. scope String()..Append(srcPath));
|
||||
if (sourceViewPanelMap.TryGet(cmpPath, var matchKey, var sourceViewPanel))
|
||||
{
|
||||
gApp.FileRenamed(sourceViewPanel.mProjectSource, srcPath, destPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (File.Copy(srcPath, destPath) case .Err)
|
||||
{
|
||||
gApp.Fail(scope $"Failed to copy '{srcPath}' to '{destPath}'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void KeyDown(KeyCode keyCode, bool isRepeat)
|
||||
{
|
||||
mListView.KeyDown(keyCode, isRepeat);
|
||||
|
@ -2067,28 +2380,11 @@ namespace IDE.ui
|
|||
switch (keyCode)
|
||||
{
|
||||
case (.)'C':
|
||||
|
||||
String clipData = scope .();
|
||||
mListView.GetRoot().WithSelectedItems(scope (selectedItem) =>
|
||||
{
|
||||
if (mListViewToProjectMap.GetValue(selectedItem) case .Ok(var sourceProjectItem))
|
||||
{
|
||||
if (var projectSource = sourceProjectItem as ProjectSource)
|
||||
{
|
||||
var path = scope String();
|
||||
sourceProjectItem.mProject.GetProjectFullPath(projectSource.mPath, path);
|
||||
path.Replace('\\', '/');
|
||||
|
||||
if (!clipData.IsEmpty)
|
||||
clipData.Append("\n");
|
||||
clipData.Append("file:///");
|
||||
IDEUtils.URLEncode(path, clipData);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!clipData.IsEmpty)
|
||||
gApp.SetClipboardData("code/file-list", clipData.Ptr, (.)clipData.Length, true);
|
||||
CopyToClipboard();
|
||||
case (.)'X':
|
||||
CutToClipboard();
|
||||
case (.)'V':
|
||||
PasteFromClipboard();
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
@ -2943,12 +3239,6 @@ namespace IDE.ui
|
|||
{
|
||||
Regenerate(false);
|
||||
});
|
||||
|
||||
item = gApp.AddMenuItem(menu, "Duplicate", "Duplicate Item");
|
||||
item.mOnMenuItemSelected.Add(new (item) =>
|
||||
{
|
||||
|
||||
});
|
||||
}
|
||||
else if (let projectFolder = projectItem as ProjectFolder)
|
||||
{
|
||||
|
@ -3037,6 +3327,14 @@ namespace IDE.ui
|
|||
|
||||
if (!isFailedLoad)
|
||||
{
|
||||
item = menu.AddItem("Copy|Ctrl+C");
|
||||
item.mOnMenuItemSelected.Add(new (item) => CopyToClipboard());
|
||||
item = menu.AddItem("Cut|Ctrl+X");
|
||||
item.mOnMenuItemSelected.Add(new (item) => CopyToClipboard());
|
||||
item = menu.AddItem("Paste|Ctrl+V");
|
||||
item.mOnMenuItemSelected.Add(new (item) => CopyToClipboard());
|
||||
menu.AddItem();
|
||||
|
||||
item = menu.AddItem("New Folder");
|
||||
item.mOnMenuItemSelected.Add(new (item) =>
|
||||
{
|
||||
|
@ -3209,6 +3507,8 @@ namespace IDE.ui
|
|||
mImportInstalledDeferred = false;
|
||||
ImportInstalledProject();
|
||||
}
|
||||
|
||||
ValidateCutClipboard();
|
||||
}
|
||||
|
||||
public override void Resize(float x, float y, float width, float height)
|
||||
|
@ -3225,3 +3525,5 @@ namespace IDE.ui
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////
|
Loading…
Add table
Add a link
Reference in a new issue