mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-09-28 16:45:38 +00:00
Add a remote/virtual open file dialog for browsing over the network
* This needs some optimisation - the obvious one being to batch up requests for multiple children all at once.
This commit is contained in:
@@ -0,0 +1,659 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using renderdoc;
|
||||
using renderdocui.Code;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace renderdocui.Windows.Dialogs
|
||||
{
|
||||
public partial class VirtualOpenFileDialog : Form
|
||||
{
|
||||
private const int ICON_DRIVE = 0;
|
||||
private const int ICON_DIRECTORY = 1;
|
||||
private const int ICON_DIRECTORY_FADED = 2;
|
||||
private const int ICON_FILE = 3;
|
||||
private const int ICON_FILE_FADED = 4;
|
||||
private const int ICON_EXECUTABLE = 5;
|
||||
private const int ICON_EXECUTABLE_FADED = 6;
|
||||
|
||||
private const int TYPE_EXES = 0;
|
||||
private const int TYPE_ALL = 1;
|
||||
|
||||
private RenderManager m_RM = null;
|
||||
private List<DirectoryFileTreeNode> currentFiles = new List<DirectoryFileTreeNode>();
|
||||
private string currentDir = "";
|
||||
private List<string> history = new List<string>();
|
||||
private int historyidx = 0;
|
||||
|
||||
private List<DirectoryFileTreeNode> roots = new List<DirectoryFileTreeNode>();
|
||||
|
||||
#region Events
|
||||
|
||||
private static readonly object FileOkEvent = new object();
|
||||
public event EventHandler<FileOpenedEventArgs> Opened
|
||||
{
|
||||
add { Events.AddHandler(FileOkEvent, value); }
|
||||
remove { Events.RemoveHandler(FileOkEvent, value); }
|
||||
}
|
||||
protected virtual void OnOpened(FileOpenedEventArgs e)
|
||||
{
|
||||
EventHandler<FileOpenedEventArgs> handler = (EventHandler<FileOpenedEventArgs>)Events[FileOkEvent];
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public VirtualOpenFileDialog(RenderManager rm)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
m_RM = rm;
|
||||
|
||||
showHidden.Checked = false;
|
||||
fileType.SelectedIndex = 0;
|
||||
back.Enabled = forward.Enabled = up.Enabled = false;
|
||||
}
|
||||
|
||||
private string Directory { get { return NTPaths ? currentDir.Replace('/', '\\') : currentDir; } }
|
||||
private string FileName { get { return filename.Text.Trim(); } }
|
||||
|
||||
private bool m_DirBrowse = false;
|
||||
public bool DirectoryBrowse
|
||||
{
|
||||
set
|
||||
{
|
||||
m_DirBrowse = value;
|
||||
Text = "Browse for folder";
|
||||
open.Text = "Select folder";
|
||||
}
|
||||
}
|
||||
|
||||
public string ChosenPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Directory + (NTPaths ? "\\" : "/") + FileName;
|
||||
}
|
||||
}
|
||||
|
||||
private bool NTPaths = false;
|
||||
|
||||
private bool IsRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
// X: (we remove trailing slashes)
|
||||
return NTPaths ? currentDir.Length == 2 : currentDir == "/";
|
||||
}
|
||||
}
|
||||
|
||||
private DirectoryFileTreeNode GetNode(string path)
|
||||
{
|
||||
if (NTPaths)
|
||||
{
|
||||
for (int i = 0; i < roots.Count; i++)
|
||||
{
|
||||
if (roots[i].Filename == path)
|
||||
return roots[i];
|
||||
|
||||
if (roots[i].Filename[0] == path[0])
|
||||
return roots[i].GetNode(path.Substring(3));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (path == "/" || path == "")
|
||||
return roots[0];
|
||||
|
||||
return roots[0].GetNode(path.Substring(1));
|
||||
}
|
||||
}
|
||||
|
||||
private void InitialPopulate()
|
||||
{
|
||||
bool populated = false;
|
||||
|
||||
m_RM.GetHomeFolder((string path, DirectoryFile[] f) =>
|
||||
{
|
||||
currentDir = path;
|
||||
history.Add(currentDir);
|
||||
|
||||
// check if we're NT paths or unix paths, and populate the roots
|
||||
if (currentDir[0].IsAlpha() && currentDir[1] == ':')
|
||||
{
|
||||
NTPaths = true;
|
||||
|
||||
m_RM.ListFolder("/", (string p, DirectoryFile[] files) =>
|
||||
{
|
||||
foreach (var root in files)
|
||||
{
|
||||
DirectoryFileTreeNode node = new DirectoryFileTreeNode();
|
||||
node.Path = node.Filename = root.filename;
|
||||
node.IsDirectory = true;
|
||||
roots.Add(node);
|
||||
|
||||
m_RM.ListFolder(root.filename, (string a, DirectoryFile[] b) => { node.Populate(a, b); });
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
NTPaths = false;
|
||||
|
||||
DirectoryFileTreeNode node = new DirectoryFileTreeNode();
|
||||
node.Path = node.Filename = "/";
|
||||
node.IsDirectory = true;
|
||||
roots.Add(node);
|
||||
m_RM.ListFolder("/", (string a, DirectoryFile[] b) => { node.Populate(a, b); });
|
||||
}
|
||||
|
||||
// we will populate the rest of the folders when expanding to the current directory
|
||||
|
||||
populated = true;
|
||||
|
||||
BeginInvoke(new Action(UpdateViewsFromData));
|
||||
});
|
||||
|
||||
// wait a second for the results to come in
|
||||
SpinWait.SpinUntil((Func<bool>)(() => { return populated; }), 1000);
|
||||
}
|
||||
|
||||
private void PopulateNode(TreeNode treenode)
|
||||
{
|
||||
DirectoryFileTreeNode node = treenode.Tag as DirectoryFileTreeNode;
|
||||
|
||||
if (node.ChildrenPopulated || node.children.Count == 0)
|
||||
return;
|
||||
|
||||
node.ChildrenPopulated = true;
|
||||
|
||||
m_RM.ListFolder(node.children[0].Path, (string path, DirectoryFile[] files) =>
|
||||
{
|
||||
node.children[0].Populate(path, files);
|
||||
|
||||
// do the rest on-thread
|
||||
for(int i = 1; i < node.children.Count; i++)
|
||||
{
|
||||
if (!node.children[i].IsDirectory || node.children[i].HasPopulated)
|
||||
continue;
|
||||
|
||||
m_RM.ListFolder(node.children[i].Path, (string a, DirectoryFile[] b) =>
|
||||
{
|
||||
node.children[i].Populate(a, b);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
BeginInvoke(new Action(UpdateViewsFromData));
|
||||
}
|
||||
|
||||
private void AddDirTreeNodeRecursive(DirectoryFileTreeNode node, TreeNode parent)
|
||||
{
|
||||
if (!node.IsDirectory)
|
||||
return;
|
||||
|
||||
if (node.directoryNode == null)
|
||||
{
|
||||
TreeNode treenode = new TreeNode();
|
||||
treenode.Text = node.Filename;
|
||||
treenode.SelectedImageIndex = treenode.ImageIndex = ICON_DIRECTORY;
|
||||
treenode.Tag = node;
|
||||
node.directoryNode = treenode;
|
||||
|
||||
parent.Nodes.Add(treenode);
|
||||
}
|
||||
|
||||
foreach (var child in node.children)
|
||||
{
|
||||
AddDirTreeNodeRecursive(child, node.directoryNode);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateViewsFromData()
|
||||
{
|
||||
location.Text = Directory;
|
||||
|
||||
directoryTree.BeginUpdate();
|
||||
|
||||
foreach (var root in roots)
|
||||
{
|
||||
if (root.directoryNode == null)
|
||||
{
|
||||
TreeNode treenode = new TreeNode();
|
||||
treenode.Text = root.Filename;
|
||||
treenode.SelectedImageIndex = treenode.ImageIndex = ICON_DRIVE;
|
||||
treenode.Tag = root;
|
||||
root.directoryNode = treenode;
|
||||
|
||||
directoryTree.Nodes.Add(treenode);
|
||||
}
|
||||
|
||||
AddDirTreeNodeRecursive(root, root.directoryNode);
|
||||
}
|
||||
|
||||
directoryTree.EndUpdate();
|
||||
|
||||
string[] components = Directory.Split('/');
|
||||
|
||||
// expand down to the current directory
|
||||
for (int i = 1; i < components.Length; i++)
|
||||
{
|
||||
string dir = NTPaths ? components[0] : "";
|
||||
|
||||
for (int j = 1; j < i; j++)
|
||||
dir += "/" + components[j];
|
||||
|
||||
if (dir == "")
|
||||
dir = "/";
|
||||
|
||||
DirectoryFileTreeNode node = GetNode(dir);
|
||||
node.directoryNode.Expand();
|
||||
}
|
||||
|
||||
directoryTree.SelectedNode = GetNode(Directory).directoryNode;
|
||||
|
||||
UpdateFileList();
|
||||
|
||||
up.Enabled = !IsRoot;
|
||||
back.Enabled = history.Count > 0 && historyidx > 0;
|
||||
forward.Enabled = historyidx < history.Count - 1;
|
||||
}
|
||||
|
||||
private bool ChangeDirectory(string dir, bool recordHistory)
|
||||
{
|
||||
if (Directory == dir)
|
||||
return true;
|
||||
|
||||
// normalise input
|
||||
dir = dir.Replace('\\', '/');
|
||||
if (dir[dir.Length - 1] == '/')
|
||||
dir = dir.Substring(0, dir.Length - 1);
|
||||
|
||||
DirectoryFileTreeNode node = GetNode(dir);
|
||||
|
||||
if (node != null && node.AccessDenied)
|
||||
{
|
||||
ShowAccessDeniedMessage(dir);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
bool immediateError = false;
|
||||
|
||||
m_RM.ListFolder(dir, (string a, DirectoryFile[] b) =>
|
||||
{
|
||||
if (b.Length == 1 && b[0].flags.HasFlag(DirectoryFileProperty.ErrorInvalidPath))
|
||||
{
|
||||
ShowFileNotFoundMessage(dir);
|
||||
immediateError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (b.Length == 1 && b[0].flags.HasFlag(DirectoryFileProperty.ErrorAccessDenied))
|
||||
{
|
||||
ShowAccessDeniedMessage(dir);
|
||||
immediateError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (recordHistory)
|
||||
{
|
||||
if (historyidx < history.Count - 1)
|
||||
history.RemoveRange(historyidx, history.Count - 1 - historyidx);
|
||||
|
||||
history.Add(dir);
|
||||
historyidx = history.Count - 1;
|
||||
}
|
||||
currentDir = dir;
|
||||
|
||||
UpdateViewsFromData();
|
||||
});
|
||||
|
||||
if (immediateError)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recordHistory)
|
||||
{
|
||||
if (historyidx < history.Count - 1)
|
||||
history.RemoveRange(historyidx, history.Count - 1 - historyidx);
|
||||
|
||||
history.Add(dir);
|
||||
historyidx = history.Count - 1;
|
||||
}
|
||||
currentDir = dir;
|
||||
|
||||
UpdateViewsFromData();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ChangeDirectory(string dir)
|
||||
{
|
||||
return ChangeDirectory(dir, true);
|
||||
}
|
||||
|
||||
private void UpdateFileList()
|
||||
{
|
||||
fileList.Items.Clear();
|
||||
currentFiles.Clear();
|
||||
|
||||
// return, we haven't populated anything yet
|
||||
if (roots.Count == 0)
|
||||
return;
|
||||
|
||||
DirectoryFileTreeNode node = GetNode(Directory);
|
||||
|
||||
if (node == null)
|
||||
return;
|
||||
|
||||
string match = ".*";
|
||||
|
||||
if (FileName.IndexOf('*') != -1 || FileName.IndexOf('?') != -1)
|
||||
match = Regex.Escape(FileName).Replace( @"\*", ".*" ).Replace( @"\?", "." );
|
||||
|
||||
foreach (var child in node.children)
|
||||
{
|
||||
if (child.IsHidden && !showHidden.Checked)
|
||||
continue;
|
||||
|
||||
var res = Regex.Match(child.Filename, match);
|
||||
|
||||
// always display directories even if they don't match a glob
|
||||
if (!res.Success && !child.IsDirectory)
|
||||
continue;
|
||||
|
||||
int imgidx = ICON_FILE;
|
||||
if (child.IsDirectory)
|
||||
imgidx = ICON_DIRECTORY;
|
||||
else if (child.IsExecutable)
|
||||
imgidx = ICON_EXECUTABLE;
|
||||
|
||||
// skip non-executables
|
||||
if(imgidx == ICON_FILE && fileType.SelectedIndex == 0)
|
||||
continue;
|
||||
|
||||
if (child.IsHidden)
|
||||
{
|
||||
imgidx = ICON_FILE_FADED;
|
||||
if (child.IsDirectory)
|
||||
imgidx = ICON_DIRECTORY_FADED;
|
||||
else if (child.IsExecutable)
|
||||
imgidx = ICON_EXECUTABLE_FADED;
|
||||
}
|
||||
|
||||
ListViewItem item = new ListViewItem(child.Filename, imgidx);
|
||||
item.Tag = child;
|
||||
|
||||
fileList.Items.Add(item);
|
||||
|
||||
currentFiles.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryOpenFile(bool doubleclick)
|
||||
{
|
||||
// double clicking enters directories, it doesn't choose them
|
||||
if (m_DirBrowse && !doubleclick)
|
||||
{
|
||||
DirectoryFileTreeNode node = GetNode(Directory);
|
||||
|
||||
if (node != null)
|
||||
{
|
||||
OnOpened(new FileOpenedEventArgs(Directory));
|
||||
Close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int idx = currentFiles.FindIndex(f => f.Filename == FileName);
|
||||
if (idx >= 0)
|
||||
{
|
||||
if (currentFiles[idx].IsDirectory)
|
||||
{
|
||||
ChangeDirectory(Directory + "/" + currentFiles[idx].Filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
OnOpened(new FileOpenedEventArgs(ChosenPath));
|
||||
Close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ShowFileNotFoundMessage(string fn)
|
||||
{
|
||||
MessageBox.Show(String.Format("{0}\nFile not found.\nCheck the file name and try again.", fn), "File not found", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
|
||||
}
|
||||
|
||||
private void ShowAccessDeniedMessage(string fn)
|
||||
{
|
||||
MessageBox.Show(String.Format("{0} is not accessible\n\nAccess is denied.", fn), "Access is denied", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
#region Handlers
|
||||
|
||||
private void VirtualOpenFileDialog_Load(object sender, EventArgs e)
|
||||
{
|
||||
InitialPopulate();
|
||||
}
|
||||
|
||||
private void showHidden_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
UpdateFileList();
|
||||
}
|
||||
|
||||
private void fileType_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
UpdateFileList();
|
||||
}
|
||||
|
||||
private void filename_KeyPress(object sender, KeyPressEventArgs e)
|
||||
{
|
||||
if (e.KeyChar == '\r' || e.KeyChar == '\n')
|
||||
{
|
||||
if (TryOpenFile(false))
|
||||
return;
|
||||
|
||||
if (FileName.IndexOf('*') == -1 && FileName.IndexOf('?') == -1)
|
||||
{
|
||||
ShowFileNotFoundMessage(FileName);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateFileList();
|
||||
}
|
||||
}
|
||||
|
||||
private void open_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (TryOpenFile(false))
|
||||
return;
|
||||
else if (FileName != "")
|
||||
ShowFileNotFoundMessage(FileName);
|
||||
}
|
||||
|
||||
private void cancel_Click(object sender, EventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void location_KeyPress(object sender, KeyPressEventArgs e)
|
||||
{
|
||||
if (e.KeyChar == '\r' || e.KeyChar == '\n')
|
||||
{
|
||||
ChangeDirectory(location.Text);
|
||||
}
|
||||
}
|
||||
|
||||
private void directoryTree_BeforeSelect(object sender, TreeViewCancelEventArgs e)
|
||||
{
|
||||
// for unix paths we need to drop the leading /
|
||||
bool success = true;
|
||||
|
||||
if(!NTPaths)
|
||||
success = ChangeDirectory(e.Node.FullPath.Substring(1));
|
||||
else
|
||||
success = ChangeDirectory(e.Node.FullPath);
|
||||
|
||||
if (!success)
|
||||
e.Cancel = true;
|
||||
}
|
||||
|
||||
private void directoryTree_BeforeExpand(object sender, TreeViewCancelEventArgs e)
|
||||
{
|
||||
PopulateNode(e.Node);
|
||||
}
|
||||
|
||||
private void back_Click(object sender, EventArgs e)
|
||||
{
|
||||
historyidx--;
|
||||
ChangeDirectory(history[historyidx], false);
|
||||
}
|
||||
|
||||
private void forward_Click(object sender, EventArgs e)
|
||||
{
|
||||
historyidx++;
|
||||
ChangeDirectory(history[historyidx], false);
|
||||
}
|
||||
|
||||
private void up_Click(object sender, EventArgs e)
|
||||
{
|
||||
string dir = Directory;
|
||||
|
||||
int idx = dir.LastIndexOf('/');
|
||||
|
||||
if (idx == 0)
|
||||
ChangeDirectory("/");
|
||||
else if (idx >= 0)
|
||||
ChangeDirectory(dir.Substring(0, idx));
|
||||
}
|
||||
|
||||
private void fileList_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
if(fileList.SelectedItems.Count == 1)
|
||||
{
|
||||
filename.Text = fileList.SelectedItems[0].Text;
|
||||
|
||||
if (TryOpenFile(true))
|
||||
return;
|
||||
else
|
||||
ShowFileNotFoundMessage(FileName);
|
||||
}
|
||||
}
|
||||
|
||||
private void fileList_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
if(fileList.SelectedItems.Count > 0)
|
||||
filename.Text = fileList.SelectedItems[0].Text;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class FileOpenedEventArgs : EventArgs
|
||||
{
|
||||
private string m_fn;
|
||||
|
||||
public FileOpenedEventArgs(string fn)
|
||||
{
|
||||
m_fn = fn;
|
||||
}
|
||||
|
||||
public string FileName
|
||||
{
|
||||
get { return m_fn; }
|
||||
}
|
||||
}
|
||||
|
||||
public class DirectoryFileTreeNode
|
||||
{
|
||||
public string Filename = "";
|
||||
public string Path = "";
|
||||
public bool IsHidden = false;
|
||||
public bool IsExecutable = false;
|
||||
public bool IsDirectory = false;
|
||||
|
||||
public bool AccessDenied = false;
|
||||
|
||||
public TreeNode directoryNode = null;
|
||||
|
||||
public bool HasPopulated = false;
|
||||
public bool ChildrenPopulated = false;
|
||||
public List<DirectoryFileTreeNode> children = new List<DirectoryFileTreeNode>();
|
||||
|
||||
public void Populate(string path, DirectoryFile[] files)
|
||||
{
|
||||
HasPopulated = true;
|
||||
|
||||
if (files.Length == 1 && files[0].flags.HasFlag(DirectoryFileProperty.ErrorAccessDenied))
|
||||
{
|
||||
AccessDenied = true;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DirectoryFile file in files)
|
||||
{
|
||||
DirectoryFileTreeNode child = new DirectoryFileTreeNode();
|
||||
child.Filename = file.filename;
|
||||
if (Path == "/")
|
||||
child.Path = Path + file.filename;
|
||||
else
|
||||
child.Path = Path + "/" + file.filename;
|
||||
child.IsHidden = file.flags.HasFlag(DirectoryFileProperty.Hidden);
|
||||
child.IsExecutable = file.flags.HasFlag(DirectoryFileProperty.Executable);
|
||||
child.IsDirectory = file.flags.HasFlag(DirectoryFileProperty.Directory);
|
||||
children.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
public DirectoryFileTreeNode GetNode(string path)
|
||||
{
|
||||
int idx = path.IndexOf('/');
|
||||
|
||||
string dirname = "";
|
||||
string rest = "";
|
||||
|
||||
if (idx >= 0)
|
||||
{
|
||||
rest = path.Substring(idx + 1);
|
||||
dirname = path.Substring(0, idx);
|
||||
}
|
||||
|
||||
foreach (DirectoryFileTreeNode child in children)
|
||||
{
|
||||
if (child.Filename == path)
|
||||
return child;
|
||||
else if (child.Filename == dirname)
|
||||
return child.GetNode(rest);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user