mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-23 20:32:34 +00:00
7febb2434f
DECORATE: added "$obsolete" special comment. When present, it marks the thing as obsolete. It will be detected by "Check obsolete thing" Map Analysis Mode check and will be marked in the Thing Properties Window and the Thing Info panel. Map Analysis mode: added "Check obsolete things" check. Updated documentation ("DECORATE keys" page). Updated ZDoom_DECORATE.cfg.
484 lines
13 KiB
C#
484 lines
13 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.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 ================== Constants
|
|
|
|
private const int WARNING_ICON_INDEX = 40; //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 >= 0) && (ti.Color < thingimages.Images.Count)) 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 = 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;
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
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.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();
|
|
foreach(TreeNode node in nodes)
|
|
{
|
|
if(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));
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|