UltimateZoneBuilder/Source/Core/Controls/ThingBrowserControl.cs
MaxED 5a7b599a46 Added: nested thing categories can now be defined in DECORATE using "//$Category" special comment. Syntax is the same as in SLADE 3.
Added: thing categories defined in Game Configurations can now be nested.
Changed, Thing Edit window: thing categories now use different icons.
Fixed: Thing Edit and Vertex edit windows had incorrect help links.
Fixed: Sound Propagation and Sound Environment modes had incorrect help links.
Documentation: updated "DECORATE keys" and "Things Settings" pages.
2015-06-01 21:46:28 +00:00

425 lines
11 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.Windows.Forms;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.GZBuilder.Controls;
#endregion
namespace CodeImp.DoomBuilder.Controls
{
public partial class ThingBrowserControl : UserControl
{
#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
private void AddThingCategories(ICollection<ThingCategory> categories, TreeNodeCollection collection)
{
foreach(ThingCategory tc in categories)
{
// Create category
TreeNode cn = collection.Add(tc.Name, tc.Title);
cn.ImageIndex = thingimages.Images.Count / 2; // Offset to folder icons
if((tc.Color >= 0) && (tc.Color < thingimages.Images.Count)) cn.ImageIndex += tc.Color;
cn.SelectedImageIndex = cn.ImageIndex;
// Create subcategories
AddThingCategories(tc.Children, cn.Nodes);
// Create things
foreach(ThingTypeInfo ti in tc.Things)
{
// Create thing
TreeNode n = cn.Nodes.Add(ti.Title);
if((ti.Color >= 0) && (ti.Color < thingimages.Images.Count)) n.ImageIndex = ti.Color;
n.SelectedImageIndex = n.ImageIndex;
n.Tag = ti;
nodes.Add(n);
}
}
}
#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
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
{
n.Parent.Expand();
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
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))
{
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();
foreach(TreeNode node in nodes)
{
if(node.Text.ToUpperInvariant().Contains(match))
{
typelist.Nodes.Add(node);
}
}
doupdatenode = true;
doupdatetextbox = true;
}
typelist.ResumeLayout();
}
//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;
}
//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));
}
#endregion
}
}