1
0
Fork 0
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:
Brian Fiete 2022-08-04 08:19:01 -07:00
parent 0907bd1f52
commit 05c2e1587c
4 changed files with 529 additions and 207 deletions

View file

@ -792,9 +792,23 @@ namespace Beefy
BFApp_SetCursor((int32)cursor); 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) public virtual void ReleaseClipboardData(void* ptr)
@ -802,9 +816,9 @@ namespace Beefy
BFApp_ReleaseClipboardData(ptr); 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) public virtual bool GetClipboardText(String outStr, String extra)
@ -813,10 +827,10 @@ namespace Beefy
return GetClipboardTextData("text", outStr); return GetClipboardTextData("text", outStr);
} }
public bool GetClipboardTextData(String format, String outStr) public bool GetClipboardTextData(String format, String outStr, int waitTime = 500)
{ {
int32 aSize; int32 aSize;
void* clipboardData = GetClipboardData(format, out aSize); void* clipboardData = GetClipboardData(format, out aSize, waitTime);
if (clipboardData == null) if (clipboardData == null)
return false; return false;

File diff suppressed because it is too large Load diff

View file

@ -304,6 +304,7 @@ namespace IDE
public Color mWorkspaceFailedText = 0xFFE04040; public Color mWorkspaceFailedText = 0xFFE04040;
public Color mWorkspaceManualIncludeText = 0xFFE0E0FF; public Color mWorkspaceManualIncludeText = 0xFFE0E0FF;
public Color mWorkspaceIgnoredText = 0xFF909090; public Color mWorkspaceIgnoredText = 0xFF909090;
public Color mWorkspaceCutText = 0xFFC0B0B0;
public Color mCode = 0xFFFFFFFF; public Color mCode = 0xFFFFFFFF;
public Color mKeyword = 0xFFE1AE9A; public Color mKeyword = 0xFFE1AE9A;

View file

@ -61,6 +61,17 @@ namespace IDE.ui
else if (projectItem.mIncludeKind == .Ignore) else if (projectItem.mIncludeKind == .Ignore)
color = Color.Mult(color, gApp.mSettings.mUISettings.mColors.mWorkspaceIgnoredText); 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 (let projectSource = projectItem as ProjectSource)
{ {
if (projectSource.mLoadFailed) if (projectSource.mLoadFailed)
@ -146,6 +157,7 @@ namespace IDE.ui
public bool mShowIgnored = true; public bool mShowIgnored = true;
public bool mSortDirty; public bool mSortDirty;
public bool mWantsRehup; public bool mWantsRehup;
public HashSet<String> mClipboardCutQueued ~ DeleteContainerAndItems!(_);
public this() 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) public override void KeyDown(KeyCode keyCode, bool isRepeat)
{ {
mListView.KeyDown(keyCode, isRepeat); mListView.KeyDown(keyCode, isRepeat);
@ -2067,28 +2380,11 @@ namespace IDE.ui
switch (keyCode) switch (keyCode)
{ {
case (.)'C': case (.)'C':
CopyToClipboard();
String clipData = scope .(); case (.)'X':
mListView.GetRoot().WithSelectedItems(scope (selectedItem) => CutToClipboard();
{
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);
case (.)'V': case (.)'V':
PasteFromClipboard();
default: default:
} }
} }
@ -2943,12 +3239,6 @@ namespace IDE.ui
{ {
Regenerate(false); Regenerate(false);
}); });
item = gApp.AddMenuItem(menu, "Duplicate", "Duplicate Item");
item.mOnMenuItemSelected.Add(new (item) =>
{
});
} }
else if (let projectFolder = projectItem as ProjectFolder) else if (let projectFolder = projectItem as ProjectFolder)
{ {
@ -3037,6 +3327,14 @@ namespace IDE.ui
if (!isFailedLoad) 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 = menu.AddItem("New Folder");
item.mOnMenuItemSelected.Add(new (item) => item.mOnMenuItemSelected.Add(new (item) =>
{ {
@ -3209,6 +3507,8 @@ namespace IDE.ui
mImportInstalledDeferred = false; mImportInstalledDeferred = false;
ImportInstalledProject(); ImportInstalledProject();
} }
ValidateCutClipboard();
} }
public override void Resize(float x, float y, float width, float height) public override void Resize(float x, float y, float width, float height)
@ -3225,3 +3525,5 @@ namespace IDE.ui
} }
} }
///////////////////////