mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-23 12:22:35 +00:00
532 lines
14 KiB
C#
532 lines
14 KiB
C#
|
|
#region ================== Copyright (c) 2007 Pascal vd Heiden
|
|
|
|
/*
|
|
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
|
|
* This program is released under GNU General Public License
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#endregion
|
|
|
|
#region ================== Namespaces
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Globalization;
|
|
using System.Windows.Forms;
|
|
using CodeImp.DoomBuilder.Config;
|
|
using CodeImp.DoomBuilder.Data;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder.Controls
|
|
{
|
|
public partial class ThingBrowserControl : UserControl
|
|
{
|
|
#region ================== Constants
|
|
|
|
private const int WARNING_ICON_INDEX = 20; //mxd
|
|
private const int FOLDER_ICON_OFFSET = 21; //mxd
|
|
private const int FOLDER_OPEN_ICON_OFFSET = 41; //mxd
|
|
|
|
#endregion
|
|
|
|
#region ================== Events
|
|
|
|
public delegate void TypeChangedDeletegate(ThingTypeInfo value);
|
|
public delegate void TypeDoubleClickDeletegate();
|
|
|
|
public event TypeChangedDeletegate OnTypeChanged;
|
|
public event TypeDoubleClickDeletegate OnTypeDoubleClicked;
|
|
|
|
#endregion
|
|
|
|
#region ================== Variables
|
|
|
|
private List<TreeNode> nodes;
|
|
private List<TreeNode> validnodes; //mxd
|
|
private ThingTypeInfo thinginfo;
|
|
private bool doupdatenode;
|
|
private bool doupdatetextbox;
|
|
private TreeNode doubleclickednode; //mxd
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
public string TypeStringValue { get { return typeid.Text; } }
|
|
public bool UseMultiSelection { get { return typelist.SelectionMode == TreeViewSelectionMode.MultiSelectSameLevel; } set { typelist.SelectionMode = (value ? TreeViewSelectionMode.MultiSelectSameLevel : TreeViewSelectionMode.SingleSelect); } }
|
|
|
|
#endregion
|
|
|
|
#region ================== Constructor
|
|
|
|
// Constructor
|
|
public ThingBrowserControl()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
// This sets up the control
|
|
public void Setup()
|
|
{
|
|
// Go for all predefined categories
|
|
typelist.Nodes.Clear();
|
|
nodes = new List<TreeNode>();
|
|
validnodes = new List<TreeNode>(); //mxd
|
|
AddThingCategories(General.Map.Data.ThingCategories, typelist.Nodes); //mxd
|
|
doupdatenode = true;
|
|
doupdatetextbox = true;
|
|
}
|
|
|
|
//mxd. This recursively creates thing category tree nodes. Returns true when a thing in this category is obsolete
|
|
private bool AddThingCategories(ICollection<ThingCategory> categories, TreeNodeCollection collection)
|
|
{
|
|
bool containsobsoletethings = false;
|
|
|
|
foreach(ThingCategory tc in categories)
|
|
{
|
|
// Create category
|
|
TreeNode cn = collection.Add(tc.Name, tc.Title);
|
|
|
|
// Create subcategories
|
|
bool isobsolete = AddThingCategories(tc.Children, cn.Nodes);
|
|
|
|
// Create things
|
|
foreach(ThingTypeInfo ti in tc.Things)
|
|
{
|
|
// Create thing
|
|
TreeNode n = cn.Nodes.Add(ti.Title);
|
|
n.Tag = ti;
|
|
|
|
if(ti.IsObsolete)
|
|
{
|
|
n.Text += " - OBSOLETE";
|
|
n.BackColor = Color.MistyRose;
|
|
n.ToolTipText = ti.ObsoleteMessage;
|
|
|
|
// Set warning icon
|
|
n.ImageIndex = WARNING_ICON_INDEX;
|
|
n.SelectedImageIndex = WARNING_ICON_INDEX;
|
|
isobsolete = true;
|
|
}
|
|
else
|
|
{
|
|
// Set regular icon
|
|
if((ti.Color > -1) && (ti.Color < WARNING_ICON_INDEX)) n.ImageIndex = ti.Color;
|
|
n.SelectedImageIndex = n.ImageIndex;
|
|
}
|
|
|
|
nodes.Add(n);
|
|
}
|
|
|
|
// Set category icon
|
|
containsobsoletethings |= isobsolete;
|
|
if(isobsolete)
|
|
{
|
|
cn.BackColor = Color.MistyRose;
|
|
cn.ImageIndex = WARNING_ICON_INDEX;
|
|
cn.SelectedImageIndex = WARNING_ICON_INDEX;
|
|
}
|
|
else
|
|
{
|
|
cn.ImageIndex = FOLDER_ICON_OFFSET; // Offset to folder icons
|
|
if((tc.Color > -1) && (tc.Color < WARNING_ICON_INDEX)) cn.ImageIndex += tc.Color;
|
|
cn.SelectedImageIndex = cn.ImageIndex;
|
|
}
|
|
}
|
|
|
|
return containsobsoletethings;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Methods
|
|
|
|
// Select a type
|
|
public void SelectType(int type)
|
|
{
|
|
// Set type index
|
|
typeid.Text = type.ToString();
|
|
typeid_TextChanged(this, EventArgs.Empty);
|
|
}
|
|
|
|
// Return selected type info
|
|
public ThingTypeInfo GetSelectedInfo()
|
|
{
|
|
return thinginfo;
|
|
}
|
|
|
|
// This clears the type
|
|
public void ClearSelectedType()
|
|
{
|
|
doupdatenode = false;
|
|
|
|
// Clear selection
|
|
typelist.SelectedNodes.Clear(); //mxd
|
|
validnodes.Clear(); //mxd
|
|
typeid.Text = "";
|
|
|
|
// Collapse nodes
|
|
foreach(TreeNode n in nodes)
|
|
if(n.Parent.IsExpanded) n.Parent.Collapse();
|
|
|
|
doupdatenode = true;
|
|
}
|
|
|
|
// Result
|
|
public int GetResult(int original)
|
|
{
|
|
//mxd. Get a random ThingTypeInfo from valid nodes?
|
|
if(typelist.SelectionMode == TreeViewSelectionMode.MultiSelectSameLevel && validnodes.Count > 0)
|
|
{
|
|
return (validnodes[General.Random(0, validnodes.Count - 1)].Tag as ThingTypeInfo).Index;
|
|
}
|
|
|
|
return typeid.GetResult(original);
|
|
}
|
|
|
|
//mxd
|
|
public void FocusTextbox()
|
|
{
|
|
tbFilter.Focus();
|
|
}
|
|
|
|
//mxd
|
|
private List<TreeNode> GetValidNodes()
|
|
{
|
|
Dictionary<string, TreeNode> vn = new Dictionary<string, TreeNode>(StringComparer.Ordinal);
|
|
foreach(TreeNode n in typelist.SelectedNodes) GetValidNodes(n, ref vn);
|
|
return new List<TreeNode>(vn.Values);
|
|
}
|
|
|
|
private static void GetValidNodes(TreeNode root, ref Dictionary<string, TreeNode> vn)
|
|
{
|
|
if(root.Nodes.Count == 0)
|
|
{
|
|
if(root.Tag is ThingTypeInfo && !vn.ContainsKey(root.Text)) vn.Add(root.Text, root);
|
|
}
|
|
else
|
|
{
|
|
foreach(TreeNode n in root.Nodes) GetValidNodes(n, ref vn);
|
|
}
|
|
}
|
|
|
|
// Update preview image (mxd)
|
|
private void UpdateThingSprite()
|
|
{
|
|
if(General.Map == null) return;
|
|
|
|
if(thinginfo != null)
|
|
{
|
|
if(thinginfo.Sprite.ToLowerInvariant().StartsWith(DataManager.INTERNAL_PREFIX) &&
|
|
(thinginfo.Sprite.Length > DataManager.INTERNAL_PREFIX.Length))
|
|
{
|
|
spritetex.Image = General.Map.Data.GetSpriteImage(thinginfo.Sprite).GetBitmap();
|
|
return;
|
|
}
|
|
|
|
if((thinginfo.Sprite.Length < 9) && (thinginfo.Sprite.Length > 0))
|
|
{
|
|
ImageData sprite = General.Map.Data.GetSpriteImage(thinginfo.Sprite);
|
|
spritetex.Image = sprite.GetPreview();
|
|
if(!sprite.IsPreviewLoaded) updatetimer.Start();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//Show Mixed Things icon?
|
|
if(validnodes.Count > 1)
|
|
{
|
|
spritetex.Image = Properties.Resources.MixedThings;
|
|
return;
|
|
}
|
|
|
|
spritetex.Image = null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Events
|
|
|
|
// List double-clicked. e.Node and typelist.SelectedNodes[0] may contain incorrect node,
|
|
// so we set the correct one in typelist_AfterSelect handler (mxd)
|
|
private void typelist_MouseDoubleClick(object sender, MouseEventArgs e)
|
|
{
|
|
if(typelist.SelectedNodes.Count == 1
|
|
&& doubleclickednode != null
|
|
&& doubleclickednode.Nodes.Count == 0
|
|
&& doubleclickednode.Tag is ThingTypeInfo
|
|
&& OnTypeDoubleClicked != null
|
|
&& typeid.Text.Length > 0)
|
|
{
|
|
OnTypeDoubleClicked();
|
|
}
|
|
}
|
|
|
|
// Thing type selection changed
|
|
private void typelist_SelectionsChanged(object sender, EventArgs e)
|
|
{
|
|
doubleclickednode = null; //mxd
|
|
if(!doupdatetextbox) return;
|
|
|
|
//mxd
|
|
validnodes = GetValidNodes();
|
|
|
|
//mxd. Got a valid multiselection? Well, can't show any useful info about that...
|
|
if(typelist.SelectionMode == TreeViewSelectionMode.MultiSelectSameLevel && validnodes.Count > 1)
|
|
{
|
|
doupdatenode = false;
|
|
if(!string.IsNullOrEmpty(typeid.Text))
|
|
{
|
|
// Event will be raised in typeid_OnTextChanged
|
|
typeid.Text = "";
|
|
}
|
|
else if(OnTypeChanged != null)
|
|
{
|
|
// Or raise event here
|
|
UpdateThingSprite();
|
|
OnTypeChanged(thinginfo);
|
|
}
|
|
doupdatenode = true;
|
|
}
|
|
else if(validnodes.Count == 1) //Anything selected?
|
|
{
|
|
// Show info
|
|
doupdatenode = false;
|
|
typeid.Text = (validnodes[0].Tag as ThingTypeInfo).Index.ToString();
|
|
doupdatenode = true;
|
|
|
|
// Set as double-clicked only if a single child node is selected
|
|
if(typelist.SelectedNodes.Count == 1 && typelist.SelectedNodes[0].Nodes.Count == 0)
|
|
{
|
|
doubleclickednode = validnodes[0]; //mxd
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UpdateThingSprite(); //mxd
|
|
}
|
|
}
|
|
|
|
// Thing type index changed
|
|
private void typeid_TextChanged(object sender, EventArgs e)
|
|
{
|
|
bool knownthing = false;
|
|
|
|
// Any text?
|
|
if(typeid.Text.Length > 0)
|
|
{
|
|
// Get the info
|
|
int typeindex = typeid.GetResult(0);
|
|
thinginfo = General.Map.Data.GetThingInfoEx(typeindex);
|
|
if(thinginfo != null)
|
|
{
|
|
knownthing = true;
|
|
|
|
// Size
|
|
sizelabel.Text = (thinginfo.Radius * 2) + " x " + thinginfo.Height;
|
|
|
|
// Hangs from ceiling
|
|
if(thinginfo.Hangs) positionlabel.Text = "Ceiling"; else positionlabel.Text = "Floor";
|
|
|
|
// Blocking
|
|
switch(thinginfo.Blocking)
|
|
{
|
|
case ThingTypeInfo.THING_BLOCKING_NONE: blockinglabel.Text = "No"; break;
|
|
case ThingTypeInfo.THING_BLOCKING_FULL: blockinglabel.Text = "Completely"; break;
|
|
case ThingTypeInfo.THING_BLOCKING_HEIGHT: blockinglabel.Text = "True-Height"; break;
|
|
default: blockinglabel.Text = "Unknown"; break;
|
|
}
|
|
}
|
|
|
|
if(doupdatenode)
|
|
{
|
|
doupdatetextbox = false;
|
|
typelist.SelectedNodes.Clear();
|
|
validnodes.Clear(); //mxd
|
|
foreach(TreeNode n in nodes)
|
|
{
|
|
// Matching node?
|
|
if((n.Tag as ThingTypeInfo).Index == typeindex)
|
|
{
|
|
// Select this
|
|
if(n.TreeView != null) //mxd. Tree node may've been removed during filtering
|
|
{
|
|
if(n.Parent != null) n.Parent.Expand(); // node won't have parent when the list is prefiltered
|
|
typelist.SelectedNodes.Add(n);
|
|
n.EnsureVisible();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
doupdatetextbox = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
thinginfo = null;
|
|
if(doupdatenode)
|
|
{
|
|
typelist.SelectedNodes.Clear();
|
|
validnodes.Clear(); //mxd
|
|
}
|
|
}
|
|
|
|
// No known thing?
|
|
if(!knownthing)
|
|
{
|
|
sizelabel.Text = "-";
|
|
positionlabel.Text = "-";
|
|
blockinglabel.Text = "-";
|
|
}
|
|
|
|
//mxd. Update help link
|
|
bool displayclassname = (thinginfo != null && !string.IsNullOrEmpty(thinginfo.ClassName) && !thinginfo.ClassName.StartsWith("$"));
|
|
classname.Enabled = (displayclassname && !string.IsNullOrEmpty(General.Map.Config.ThingClassHelp));
|
|
classname.Text = (displayclassname ? thinginfo.ClassName : "--");
|
|
labelclassname.Enabled = classname.Enabled;
|
|
|
|
// Update icon (mxd)
|
|
UpdateThingSprite();
|
|
|
|
// Raise event
|
|
if(OnTypeChanged != null) OnTypeChanged(thinginfo);
|
|
}
|
|
|
|
private void updatetimer_Tick(object sender, EventArgs e)
|
|
{
|
|
updatetimer.Stop();
|
|
UpdateThingSprite();
|
|
}
|
|
|
|
//mxd
|
|
private void typelist_MouseEnter(object sender, EventArgs e)
|
|
{
|
|
typelist.Focus();
|
|
}
|
|
|
|
//mxd. Transfer focus to Filter textbox
|
|
private void typelist_KeyPress(object sender, KeyPressEventArgs e)
|
|
{
|
|
tbFilter.Focus();
|
|
if(e.KeyChar == '\b') // Any better way to check for Backspace?..
|
|
{
|
|
if(!string.IsNullOrEmpty(tbFilter.Text) && tbFilter.SelectionStart > 0 && tbFilter.SelectionLength == 0)
|
|
{
|
|
int s = tbFilter.SelectionStart - 1;
|
|
tbFilter.Text = tbFilter.Text.Remove(s, 1);
|
|
tbFilter.SelectionStart = s;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tbFilter.AppendText(e.KeyChar.ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
}
|
|
|
|
//mxd
|
|
private void bClear_Click(object sender, EventArgs e)
|
|
{
|
|
tbFilter.Clear();
|
|
}
|
|
|
|
//mxd
|
|
private void tbFilter_TextChanged(object sender, EventArgs e)
|
|
{
|
|
typelist.SuspendLayout();
|
|
|
|
if(string.IsNullOrEmpty(tbFilter.Text.Trim()))
|
|
{
|
|
Setup();
|
|
typeid_TextChanged(this, EventArgs.Empty);
|
|
}
|
|
else
|
|
{
|
|
// Go for all predefined categories
|
|
typelist.SelectedNodes.Clear();
|
|
typelist.Nodes.Clear();
|
|
validnodes.Clear();
|
|
|
|
string match = tbFilter.Text.ToUpperInvariant();
|
|
HashSet<string> added = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
// First add nodes, which titles start with given text
|
|
foreach(TreeNode node in nodes)
|
|
{
|
|
if(node.Text.ToUpperInvariant().StartsWith(match))
|
|
{
|
|
typelist.Nodes.Add(node);
|
|
added.Add(node.Text);
|
|
}
|
|
}
|
|
|
|
// Then add nodes, which titles contain given text
|
|
foreach(TreeNode node in nodes)
|
|
{
|
|
if(!added.Contains(node.Text) && node.Text.ToUpperInvariant().Contains(match))
|
|
typelist.Nodes.Add(node);
|
|
}
|
|
|
|
doupdatenode = true;
|
|
doupdatetextbox = true;
|
|
}
|
|
|
|
typelist.ResumeLayout();
|
|
}
|
|
|
|
//mxd. Switch focus to types list?
|
|
private void tbFilter_KeyUp(object sender, KeyEventArgs e)
|
|
{
|
|
if(e.KeyCode == Keys.Down && typelist.Nodes.Count > 0)
|
|
{
|
|
typelist.SelectedNodes.Clear();
|
|
typelist.SelectedNodes.Add(typelist.Nodes[0]);
|
|
typelist.Focus();
|
|
}
|
|
}
|
|
|
|
//mxd. Because anchor-based alignment fails when using high-Dpi settings...
|
|
private void ThingBrowserControl_Resize(object sender, EventArgs e)
|
|
{
|
|
infopanel.Top = this.Height - infopanel.Height;
|
|
infopanel.Width = this.Width;
|
|
spritepanel.Left = infopanel.Width - spritepanel.Width;
|
|
typelist.Height = infopanel.Top - typelist.Top;
|
|
typelist.Width = this.Width;
|
|
bClear.Left = this.Width - bClear.Width - bClear.Margin.Right;
|
|
tbFilter.Width = bClear.Left - tbFilter.Left - bClear.Margin.Left;
|
|
}
|
|
|
|
//mxd. If it's clickable, all data is valid.
|
|
private void classname_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
|
{
|
|
General.OpenWebsite(General.Map.Config.ThingClassHelp.Replace("%K", thinginfo.ClassName));
|
|
}
|
|
|
|
//mxd. Switch to Open Folder icon
|
|
private void typelist_BeforeExpand(object sender, TreeViewCancelEventArgs e)
|
|
{
|
|
// Category node?
|
|
if(e.Node.ImageIndex > WARNING_ICON_INDEX)
|
|
e.Node.ImageIndex = e.Node.ImageIndex - FOLDER_ICON_OFFSET + FOLDER_OPEN_ICON_OFFSET;
|
|
}
|
|
|
|
//mxd. Switch to Closed Folder icon
|
|
private void typelist_BeforeCollapse(object sender, TreeViewCancelEventArgs e)
|
|
{
|
|
// Category node?
|
|
if(e.Node.ImageIndex > WARNING_ICON_INDEX)
|
|
e.Node.ImageIndex = e.Node.ImageIndex - FOLDER_OPEN_ICON_OFFSET + FOLDER_ICON_OFFSET;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|