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 currentFiles = new List(); private string currentDir = ""; private List history = new List(); private int historyidx = 0; private List roots = new List(); #region Events private static readonly object FileOkEvent = new object(); public event EventHandler Opened { add { Events.AddHandler(FileOkEvent, value); } remove { Events.RemoveHandler(FileOkEvent, value); } } protected virtual void OnOpened(FileOpenedEventArgs e) { EventHandler handler = (EventHandler)Events[FileOkEvent]; if (handler != null) handler(this, e); } #endregion public VirtualOpenFileDialog(RenderManager rm) { InitializeComponent(); Icon = global::renderdocui.Properties.Resources.icon; m_RM = rm; showHidden.Checked = false; fileType.SelectedIndex = 0; back.Enabled = forward.Enabled = up.Enabled = false; } private string Directory { get { return currentDir.Replace('\\', '/'); } } 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"; } } private string ChosenPath { get { return Directory + "/" + FileName; } } private bool NTPaths = false; private bool IsRoot { get { // X: (we remove trailing slashes) return NTPaths ? currentDir.Length == 2 : currentDir == "/"; } } private bool RemoteFailed = false; private void CheckRemote(bool success) { if (!success && !RemoteFailed) { MessageBox.Show("Remote server disconnected during browsing.\n" + "You must close this file browser and restore the connection before continuing.", "Remote server disconnected", MessageBoxButtons.OK, MessageBoxIcon.Error); RemoteFailed = true; BeginInvoke(new Action(UpdateViewsFromData)); } } private DirectoryFileTreeNode GetNode(string path) { if (NTPaths) { for (int i = 0; i < roots.Count; i++) { if (roots[i].Filename == path || roots[i].Filename[0] + ":" == 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; CheckRemote(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); CheckRemote(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); CheckRemote(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)(() => { 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; CheckRemote(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); } } bool insideUpdateViews = false; private void UpdateViewsFromData() { if(insideUpdateViews) return; insideUpdateViews = true; location.Text = Directory; if (RemoteFailed) { directoryTree.BeginUpdate(); directoryTree.Nodes.Clear(); directoryTree.EndUpdate(); up.Enabled = back.Enabled = forward.Enabled = showHidden.Enabled = fileType.Enabled = open.Enabled = filename.Enabled = location.Enabled = false; location.Text = filename.Text = ""; UpdateFileList(); return; } 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('/'); DirectoryFileTreeNode node = null; // 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 = "/"; node = GetNode(dir); if (node == null || node.directoryNode == null) break; node.directoryNode.Expand(); } node = GetNode(Directory); if (node != null && node.directoryNode != null) directoryTree.SelectedNode = node.directoryNode; UpdateFileList(); up.Enabled = !IsRoot; back.Enabled = history.Count > 0 && historyidx > 0; forward.Enabled = historyidx < history.Count - 1; insideUpdateViews = false; } private bool ChangeDirectory(string dir, bool recordHistory) { if (Directory == dir) return true; // normalise input dir = dir.Replace('\\', '/'); if (dir.Length > 0 && dir[dir.Length - 1] == '/') dir = dir.Substring(0, dir.Length - 1); if (NTPaths) dir = dir.Replace("://", ":/"); DirectoryFileTreeNode node = GetNode(dir); if (node != null && node.AccessDenied) { ShowAccessDeniedMessage(dir); return false; } if (node == null) { bool immediateError = false; CheckRemote(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.BeginUpdate(); fileList.Clear(); fileList.EndUpdate(); currentFiles.Clear(); // return, we haven't populated anything yet if (roots.Count == 0 || RemoteFailed) return; DirectoryFileTreeNode node = GetNode(Directory); if (node == null) return; string match = ".*"; if (FileName.IndexOf('*') != -1 || FileName.IndexOf('?') != -1) match = Regex.Escape(FileName).Replace( @"\*", ".*" ).Replace( @"\?", "." ); fileList.BeginUpdate(); 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); } fileList.EndUpdate(); } 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(NTPaths, 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(NTPaths, 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(bool NT, string fn) { if(NT) m_fn = fn.Replace('/', '\\'); else 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 children = new List(); 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; } } }