mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-27 14:12:16 +00:00
5dd45ffdca
Fixed, Script Editor: resources list control was incorrectly positioned when using DPI scaling.
531 lines
18 KiB
C#
531 lines
18 KiB
C#
#region ================== Namespaces
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Windows.Forms;
|
|
using CodeImp.DoomBuilder.Config;
|
|
using CodeImp.DoomBuilder.Data;
|
|
using CodeImp.DoomBuilder.Data.Scripting;
|
|
using CodeImp.DoomBuilder.Windows;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder.Controls
|
|
{
|
|
internal partial class ScriptResourcesControl : UserControl
|
|
{
|
|
|
|
#region ================== Nodes sorter
|
|
|
|
private class ScriptNodesSorter : IComparer
|
|
{
|
|
private List<string> resourceorder;
|
|
private HashSet<TextResourceNodeType> resourcetypes;
|
|
|
|
public ScriptNodesSorter()
|
|
{
|
|
// Needed to show resources in the map-defined order
|
|
resourceorder = new List<string>();
|
|
foreach(DataReader reader in General.Map.Data.Containers)
|
|
{
|
|
resourceorder.Add(reader.Location.location);
|
|
}
|
|
|
|
resourcetypes = new HashSet<TextResourceNodeType> { TextResourceNodeType.RESOURCE_DIRECTORY, TextResourceNodeType.RESOURCE_PK3, TextResourceNodeType.RESOURCE_WAD };
|
|
}
|
|
|
|
// Compare between two tree nodes
|
|
public int Compare(object o1, object o2)
|
|
{
|
|
TreeNode n1 = o1 as TreeNode;
|
|
TreeNode n2 = o2 as TreeNode;
|
|
|
|
TextResourceNodeData d1 = (TextResourceNodeData)n1.Tag;
|
|
TextResourceNodeData d2 = (TextResourceNodeData)n2.Tag;
|
|
|
|
// Sort resources by load order
|
|
if(resourcetypes.Contains(d1.NodeType) && resourcetypes.Contains(d2.NodeType))
|
|
{
|
|
int n1index = resourceorder.IndexOf(d1.ResourceLocation);
|
|
int n2index = resourceorder.IndexOf(d2.ResourceLocation);
|
|
|
|
if(n1index > n2index) return 1;
|
|
if(n1index < n2index) return -1;
|
|
return 0;
|
|
}
|
|
|
|
// Push map namespace resources before anything else
|
|
if(d1.NodeType == TextResourceNodeType.RESOURCE_MAP && d2.NodeType != TextResourceNodeType.RESOURCE_MAP) return -1;
|
|
if(d1.NodeType != TextResourceNodeType.RESOURCE_MAP && d2.NodeType == TextResourceNodeType.RESOURCE_MAP) return 1;
|
|
|
|
// Push embedded WADs before anything else except map resources
|
|
if(n1.Parent != null && n2.Parent != null)
|
|
{
|
|
if(d1.NodeType == TextResourceNodeType.RESOURCE_WAD && d2.NodeType != TextResourceNodeType.RESOURCE_WAD) return -1;
|
|
if(d1.NodeType != TextResourceNodeType.RESOURCE_WAD && d2.NodeType == TextResourceNodeType.RESOURCE_WAD) return 1;
|
|
}
|
|
|
|
// Push script folders before script files
|
|
if(d1.NodeType == TextResourceNodeType.DIRECTORY && d2.NodeType != TextResourceNodeType.DIRECTORY) return -1;
|
|
if(d1.NodeType != TextResourceNodeType.DIRECTORY && d2.NodeType == TextResourceNodeType.DIRECTORY) return 1;
|
|
|
|
// Sort by name
|
|
return n1.Text.CompareTo(n2.Text);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Constants
|
|
|
|
#endregion
|
|
|
|
#region ================== Enums
|
|
|
|
private enum TextResourceNodeType
|
|
{
|
|
RESOURCE_WAD,
|
|
RESOURCE_DIRECTORY,
|
|
RESOURCE_PK3,
|
|
RESOURCE_MAP,
|
|
DIRECTORY,
|
|
NODE,
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Structs
|
|
|
|
private struct TextResourceNodeData
|
|
{
|
|
public ScriptResource Resource;
|
|
public string ResourceLocation; // Where PK3/WAD/Folder resource is located
|
|
public string LocationInResource; // Path to text file inside resource
|
|
public TextResourceNodeType NodeType;
|
|
public ScriptType ScriptType;
|
|
|
|
public override string ToString()
|
|
{
|
|
return (NodeType == TextResourceNodeType.NODE
|
|
? Path.Combine(ResourceLocation, LocationInResource) + (Resource.LumpIndex != -1 ? ":" + Resource.LumpIndex : "")
|
|
: ResourceLocation);
|
|
}
|
|
}
|
|
|
|
private struct ScriptTypeItem
|
|
{
|
|
public string Name;
|
|
public ScriptType Type;
|
|
public override string ToString() { return Name; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Variables
|
|
|
|
private ScriptEditorPanel scriptpanel;
|
|
private Dictionary<string, Dictionary<ScriptType, HashSet<ScriptResource>>> resourcesperlocation;
|
|
private List<ScriptTypeItem> usedscripttypes;
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
#endregion
|
|
|
|
#region ================== Constructor / Setup
|
|
|
|
public ScriptResourcesControl()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
public void Setup(ScriptEditorPanel scriptpanel, Dictionary<ScriptType, HashSet<ScriptResource>> resources)
|
|
{
|
|
this.scriptpanel = scriptpanel;
|
|
projecttree.ImageList = scriptpanel.Icons.Icons; // Link icons
|
|
resourcesperlocation = new Dictionary<string, Dictionary<ScriptType, HashSet<ScriptResource>>>();
|
|
foreach(HashSet<ScriptResource> group in resources.Values)
|
|
{
|
|
foreach(ScriptResource resource in group)
|
|
{
|
|
string key = resource.Resource.Location.location;
|
|
if(!resourcesperlocation.ContainsKey(key))
|
|
resourcesperlocation.Add(key, new Dictionary<ScriptType, HashSet<ScriptResource>>());
|
|
|
|
if(!resourcesperlocation[key].ContainsKey(resource.ScriptType))
|
|
resourcesperlocation[key].Add(resource.ScriptType, new HashSet<ScriptResource>());
|
|
|
|
resourcesperlocation[key][resource.ScriptType].Add(resource);
|
|
}
|
|
}
|
|
|
|
// Add used script types to the filter combobox
|
|
string curfilter = filterbytype.SelectedText;
|
|
filterbytype.Items.Clear();
|
|
filterbytype.Items.Add(new ScriptTypeItem { Name = "All", Type = ScriptType.UNKNOWN });
|
|
|
|
usedscripttypes = new List<ScriptTypeItem>();
|
|
foreach(ScriptType st in resources.Keys)
|
|
usedscripttypes.Add(new ScriptTypeItem { Name = Enum.GetName(typeof(ScriptType), st), Type = st });
|
|
usedscripttypes.Sort((i1, i2) => String.Compare(i1.Name, i2.Name, StringComparison.Ordinal));
|
|
|
|
int toselect = 0;
|
|
for(int i = 0; i < usedscripttypes.Count; i++)
|
|
{
|
|
if(usedscripttypes[i].Name == curfilter) toselect = i;
|
|
filterbytype.Items.Add(usedscripttypes[i]);
|
|
}
|
|
filterbytype.SelectedIndex = toselect; // This will also trigger tree update
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Methods
|
|
|
|
public void SelectItem(string resourcelocation, string lumpname, int lumpindex, ScriptType scripttype)
|
|
{
|
|
TreeNode target = FindItem(projecttree.Nodes, resourcelocation, lumpname, lumpindex, scripttype);
|
|
if(target != null)
|
|
{
|
|
projecttree.SelectedNode = target;
|
|
}
|
|
}
|
|
|
|
private static TreeNode FindItem(TreeNodeCollection nodes, string resourcelocation, string lumpname, int lumpindex, ScriptType scripttype)
|
|
{
|
|
foreach(TreeNode node in nodes)
|
|
{
|
|
// Is this the item we are looking for?
|
|
TextResourceNodeData data = (TextResourceNodeData)node.Tag;
|
|
|
|
if(data.NodeType == TextResourceNodeType.NODE && data.ResourceLocation == resourcelocation
|
|
&& data.ScriptType == scripttype && data.Resource.Filename == lumpname && data.Resource.LumpIndex == lumpindex)
|
|
{
|
|
// Found it!
|
|
return node;
|
|
}
|
|
|
|
// Try children...
|
|
if(node.Nodes.Count > 0)
|
|
{
|
|
TreeNode item = FindItem(node.Nodes, resourcelocation, lumpname, lumpindex, scripttype);
|
|
if(item != null) return item;
|
|
}
|
|
}
|
|
|
|
// No dice...
|
|
return null;
|
|
}
|
|
|
|
private void UpdateResourcesTree()
|
|
{
|
|
ScriptType targettype = (filterbytype.SelectedIndex > -1 ? ((ScriptTypeItem)filterbytype.SelectedItem).Type : ScriptType.UNKNOWN);
|
|
UpdateResourcesTree(targettype, filterproject.Text);
|
|
}
|
|
|
|
private void UpdateResourcesTree(ScriptType filtertype, string filterfilename)
|
|
{
|
|
TreeNode selected = projecttree.SelectedNode;
|
|
TreeNode toselect = null;
|
|
|
|
projecttree.BeginUpdate();
|
|
projecttree.Nodes.Clear();
|
|
|
|
char[] splitter = { Path.DirectorySeparatorChar };
|
|
bool filenamefiltered = !string.IsNullOrEmpty(filterfilename);
|
|
bool filteringapplied = (filenamefiltered || filtertype != ScriptType.UNKNOWN);
|
|
|
|
// Create nodes
|
|
foreach(KeyValuePair<string, Dictionary<ScriptType, HashSet<ScriptResource>>> group in resourcesperlocation)
|
|
{
|
|
foreach(ScriptTypeItem item in usedscripttypes)
|
|
{
|
|
// Filter by script type?
|
|
if(filtertype != ScriptType.UNKNOWN && item.Type != filtertype) continue;
|
|
|
|
// Current resource has this scrit type?
|
|
if(!group.Value.ContainsKey(item.Type)) continue;
|
|
|
|
HashSet<ScriptResource> resources = group.Value[item.Type];
|
|
foreach(ScriptResource res in resources)
|
|
{
|
|
bool asreadonly = res.Resource.IsReadOnly;
|
|
|
|
// Filter by filename?
|
|
if(filenamefiltered && Path.GetFileName(res.Filename).IndexOf(filterfilename, StringComparison.OrdinalIgnoreCase) == -1)
|
|
continue;
|
|
|
|
// Resource type node added?
|
|
TreeNode root;
|
|
string key = res.Resource.Location.location;
|
|
|
|
// WAD resource inside another resource?
|
|
if(res.Resource is WADReader && ((WADReader)res.Resource).ParentResource != null)
|
|
{
|
|
WADReader wr = (WADReader)res.Resource;
|
|
string parentkey = wr.ParentResource.Location.location;
|
|
TreeNode parent = GetResourceNode(projecttree.Nodes, wr.ParentResource.Location.GetDisplayName(), parentkey, wr.ParentResource);
|
|
|
|
if(parent.Nodes.ContainsKey(key))
|
|
{
|
|
root = parent.Nodes[key];
|
|
}
|
|
else
|
|
{
|
|
root = GetResourceNode(parent.Nodes, Path.GetFileName(wr.Location.GetDisplayName()), key, res.Resource);
|
|
TrySelectNode(selected, root, ref toselect);
|
|
}
|
|
}
|
|
else if(projecttree.Nodes.ContainsKey(key))
|
|
{
|
|
root = projecttree.Nodes[key];
|
|
}
|
|
else
|
|
{
|
|
root = GetResourceNode(projecttree.Nodes, res.Resource.Location.GetDisplayName(), key, res.Resource);
|
|
TrySelectNode(selected, root, ref toselect);
|
|
}
|
|
|
|
// Single resource item or active filtering?
|
|
int iconindex = scriptpanel.Icons.GetScriptIcon(res.ScriptType);
|
|
if(filteringapplied || (resources.Count == 1 && res.ScriptType != ScriptType.ACS))
|
|
{
|
|
// Create new node
|
|
var data = new TextResourceNodeData
|
|
{
|
|
ResourceLocation = key,
|
|
LocationInResource = Path.GetDirectoryName(res.Filename),
|
|
NodeType = TextResourceNodeType.NODE,
|
|
Resource = res,
|
|
ScriptType = res.ScriptType,
|
|
};
|
|
TreeNode scriptnode = new TreeNode(res.ToString(), iconindex, iconindex) { Tag = data, ToolTipText = data.ToString() };
|
|
if(asreadonly) scriptnode.ForeColor = SystemColors.GrayText;
|
|
TrySelectNode(selected, scriptnode, ref toselect);
|
|
|
|
// Add the node
|
|
root.Nodes.Add(scriptnode);
|
|
}
|
|
else
|
|
{
|
|
// Script type added?
|
|
string typename = "[" + Enum.GetName(typeof(ScriptType), res.ScriptType) + "]";
|
|
int groupiconindex = scriptpanel.Icons.GetScriptFolderIcon(res.ScriptType, false);
|
|
|
|
TreeNode scriptroot;
|
|
if(root.Nodes.ContainsKey(typename))
|
|
{
|
|
scriptroot = root.Nodes[typename];
|
|
}
|
|
else
|
|
{
|
|
var rdata = new TextResourceNodeData
|
|
{
|
|
ResourceLocation = key,
|
|
NodeType = TextResourceNodeType.DIRECTORY,
|
|
ScriptType = res.ScriptType,
|
|
};
|
|
scriptroot = new TreeNode(typename, groupiconindex, groupiconindex)
|
|
{
|
|
Tag = rdata,
|
|
Name = typename,
|
|
ToolTipText = rdata.ToString()
|
|
};
|
|
if(asreadonly) scriptroot.ForeColor = SystemColors.GrayText;
|
|
root.Nodes.Add(scriptroot);
|
|
|
|
TrySelectNode(selected, scriptroot, ref toselect);
|
|
}
|
|
|
|
// Add the resource path nodes if needed...
|
|
string path = Path.GetDirectoryName(res.Filename);
|
|
TreeNode pathnode = scriptroot;
|
|
string localpath = string.Empty;
|
|
|
|
if(!string.IsNullOrEmpty(path))
|
|
{
|
|
List<string> parts = new List<string>(path.Split(splitter, StringSplitOptions.RemoveEmptyEntries));
|
|
while(parts.Count > 0)
|
|
{
|
|
if(pathnode.Nodes.ContainsKey(parts[0]))
|
|
{
|
|
pathnode = pathnode.Nodes[parts[0]];
|
|
}
|
|
else
|
|
{
|
|
localpath = Path.Combine(localpath, parts[0]);
|
|
|
|
TreeNode child = new TreeNode(parts[0], groupiconindex, groupiconindex);
|
|
child.Name = parts[0];
|
|
var cdata = new TextResourceNodeData
|
|
{
|
|
ResourceLocation = key,
|
|
LocationInResource = path,
|
|
NodeType = TextResourceNodeType.DIRECTORY,
|
|
ScriptType = res.ScriptType
|
|
};
|
|
child.Tag = cdata;
|
|
child.ToolTipText = cdata.ToString();
|
|
if(asreadonly) child.ForeColor = SystemColors.GrayText;
|
|
pathnode.Nodes.Add(child);
|
|
pathnode = child;
|
|
|
|
TrySelectNode(selected, pathnode, ref toselect);
|
|
}
|
|
|
|
parts.RemoveAt(0);
|
|
}
|
|
}
|
|
|
|
// Create new node
|
|
TextResourceNodeData data = new TextResourceNodeData
|
|
{
|
|
ResourceLocation = key,
|
|
LocationInResource = path,
|
|
NodeType = TextResourceNodeType.NODE,
|
|
Resource = res,
|
|
ScriptType = res.ScriptType,
|
|
};
|
|
string includepath = (res.ScriptType == ScriptType.ACS ? "\nInclude path: \"" + res.Filename + "\"" : "");
|
|
TreeNode scriptnode = new TreeNode(res.ToString(), iconindex, iconindex) { Tag = data, ToolTipText = data + includepath };
|
|
if(asreadonly) scriptnode.ForeColor = SystemColors.GrayText;
|
|
TrySelectNode(selected, scriptnode, ref toselect);
|
|
|
|
// Add the node
|
|
pathnode.Nodes.Add(scriptnode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there's only one root node, shift all nodes up
|
|
if(projecttree.Nodes.Count == 1 && projecttree.Nodes[0].Nodes.Count > 0)
|
|
{
|
|
TreeNode[] children = new TreeNode[projecttree.Nodes[0].Nodes.Count];
|
|
projecttree.Nodes[0].Nodes.CopyTo(children, 0);
|
|
|
|
projecttree.Nodes.Clear();
|
|
projecttree.Nodes.AddRange(children);
|
|
}
|
|
|
|
// Sort the nodes
|
|
projecttree.TreeViewNodeSorter = new ScriptNodesSorter();
|
|
|
|
// Have valid selection?
|
|
if(toselect != null) projecttree.SelectedNode = toselect;
|
|
|
|
// Expand all nodes when filtered
|
|
if(filenamefiltered) projecttree.ExpandAll();
|
|
|
|
projecttree.EndUpdate();
|
|
}
|
|
|
|
private TreeNode GetResourceNode(TreeNodeCollection nodes, string title, string key, DataReader resource)
|
|
{
|
|
// Node already added?
|
|
if(nodes.ContainsKey(key)) return nodes[key];
|
|
|
|
// Create new node
|
|
int resourceiconindex = scriptpanel.Icons.GetResourceIcon(resource.Location.type);
|
|
|
|
TreeNode root = new TreeNode(title, resourceiconindex, resourceiconindex);
|
|
root.Name = key;
|
|
root.Tag = new TextResourceNodeData { ResourceLocation = key, NodeType = (TextResourceNodeType)resource.Location.type };
|
|
root.ToolTipText = key;
|
|
if(resource.IsReadOnly) root.ForeColor = SystemColors.GrayText;
|
|
nodes.Add(root);
|
|
|
|
return root;
|
|
}
|
|
|
|
private static void TrySelectNode(TreeNode oldselection, TreeNode node, ref TreeNode toselect)
|
|
{
|
|
if(oldselection == null || oldselection.Text != node.Text
|
|
|| oldselection.ToolTipText != node.ToolTipText
|
|
|| oldselection.Tag.ToString() != node.Tag.ToString())
|
|
return;
|
|
|
|
// Found match!
|
|
toselect = node;
|
|
}
|
|
|
|
protected override void OnLoad(EventArgs e)
|
|
{
|
|
// Manual reposition required...
|
|
if(MainForm.DPIScaler.Width != 1.0f || MainForm.DPIScaler.Height != 1.0f)
|
|
{
|
|
filterprojectclear.Left = this.Width - filterprojectclear.Width - filterprojectclear.Margin.Right;
|
|
filterproject.Width = filterprojectclear.Left - filterprojectclear.Margin.Left - filterproject.Left;
|
|
filterbytype.Left = filterproject.Left;
|
|
filterbytype.Width = filterprojectclear.Right - filterproject.Left;
|
|
projecttree.Width = this.Width - projecttree.Left - projecttree.Margin.Right;
|
|
projecttree.Height = this.Height - projecttree.Top - projecttree.Margin.Bottom;
|
|
}
|
|
|
|
base.OnLoad(e);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Events
|
|
|
|
private void filterproject_TextChanged(object sender, EventArgs e)
|
|
{
|
|
UpdateResourcesTree();
|
|
}
|
|
|
|
private void filterprojectclear_Click(object sender, EventArgs e)
|
|
{
|
|
filterproject.Clear();
|
|
}
|
|
|
|
private void filterbytype_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
UpdateResourcesTree();
|
|
}
|
|
|
|
private void projecttree_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
|
|
{
|
|
//TODO: special handling for SCRIPTS/DIALOGUE
|
|
if(!(e.Node.Tag is TextResourceNodeData)) throw new NotSupportedException("Tag must be TextResourceData!");
|
|
TextResourceNodeData data = (TextResourceNodeData)e.Node.Tag;
|
|
|
|
// Open file
|
|
if(data.Resource != null) scriptpanel.OpenResource(data.Resource);
|
|
}
|
|
|
|
// Switch to opened resource icon
|
|
private void projecttree_BeforeExpand(object sender, TreeViewCancelEventArgs e)
|
|
{
|
|
if(!(e.Node.Tag is TextResourceNodeData)) throw new NotSupportedException("Tag must be TextResourceData!");
|
|
TextResourceNodeData data = (TextResourceNodeData)e.Node.Tag;
|
|
|
|
// Group node?
|
|
if(data.NodeType == TextResourceNodeType.DIRECTORY)
|
|
{
|
|
e.Node.ImageIndex = scriptpanel.Icons.GetScriptFolderIcon(data.ScriptType, true);
|
|
e.Node.SelectedImageIndex = e.Node.ImageIndex;
|
|
}
|
|
}
|
|
|
|
// Switch to closed resource icon
|
|
private void projecttree_BeforeCollapse(object sender, TreeViewCancelEventArgs e)
|
|
{
|
|
if(!(e.Node.Tag is TextResourceNodeData)) throw new NotSupportedException("Tag must be TextResourceData!");
|
|
TextResourceNodeData data = (TextResourceNodeData)e.Node.Tag;
|
|
|
|
// Group node?
|
|
if(data.NodeType == TextResourceNodeType.DIRECTORY)
|
|
{
|
|
e.Node.ImageIndex = scriptpanel.Icons.GetScriptFolderIcon(data.ScriptType, false);
|
|
e.Node.SelectedImageIndex = e.Node.ImageIndex;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|