From 580f7d4461638afc73600185f4f1a325ddef6d2e Mon Sep 17 00:00:00 2001
From: MaxED <j.maxed@gmail.com>
Date: Wed, 6 Apr 2016 11:44:38 +0000
Subject: [PATCH] Classic modes: further text label rendering optimization.
 MODELDEF parser: rewrote most of the parser logic. Now it picks actor
 model(s) based on Frame / FrameName properties.

---
 .../Core/GZBuilder/GZDoom/ModeldefParser.cs   | 147 +--
 .../GZBuilder/GZDoom/ModeldefStructure.cs     | 882 +++++++++---------
 Source/Core/General/Launcher.cs               |   8 +-
 Source/Core/Rendering/Renderer2D.cs           |   4 +-
 Source/Core/Rendering/TextLabel.cs            |  58 +-
 Source/Core/Windows/MainForm.cs               |  37 +-
 .../BuilderModes/ClassicModes/LinedefsMode.cs |   4 +-
 .../BuilderModes/ClassicModes/SectorsMode.cs  |   2 +-
 .../BuilderModes/ClassicModes/ThingsMode.cs   |   5 +-
 .../BuilderModes/General/LineLengthLabel.cs   |  14 +-
 10 files changed, 597 insertions(+), 564 deletions(-)

diff --git a/Source/Core/GZBuilder/GZDoom/ModeldefParser.cs b/Source/Core/GZBuilder/GZDoom/ModeldefParser.cs
index d8b3a21ec..e40b3fb14 100644
--- a/Source/Core/GZBuilder/GZDoom/ModeldefParser.cs
+++ b/Source/Core/GZBuilder/GZDoom/ModeldefParser.cs
@@ -1,34 +1,50 @@
-using System;
+#region ================== Namespaces
+
+using System;
 using System.Collections.Generic;
 using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Data;
+using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.ZDoom;
 using CodeImp.DoomBuilder.GZBuilder.Data;
+using SlimDX;
+
+#endregion
 
 namespace CodeImp.DoomBuilder.GZBuilder.GZDoom 
 {
 	internal class ModeldefParser : ZDTextParser
 	{
-		internal override ScriptType ScriptType { get { return ScriptType.MODELDEF; } }
+		#region ================== Variables
 
 		private readonly Dictionary<string, int> actorsbyclass;
-		internal Dictionary<string, int> ActorsByClass { get { return actorsbyclass; } }
-
 		private Dictionary<string, ModelData> entries; //classname, entry
+
+		#endregion
+
+		#region ================== Properties
+
+		internal override ScriptType ScriptType { get { return ScriptType.MODELDEF; } }
 		internal Dictionary<string, ModelData> Entries { get { return entries; } }
 
+		#endregion
+
+		#region ================== Constructor
+
 		internal ModeldefParser(Dictionary<string, int> actorsbyclass)
 		{
 			this.actorsbyclass = actorsbyclass;
-			this.entries = new Dictionary<string, ModelData>(StringComparer.Ordinal);
+			this.entries = new Dictionary<string, ModelData>(StringComparer.OrdinalIgnoreCase);
 		}
 
-		//should be called after all decorate actors are parsed 
+		#endregion
+
+		#region ================== Parsing
+
+		// Should be called after all decorate actors are parsed 
 		public override bool Parse(TextResourceData data, bool clearerrors)
 		{
-			entries = new Dictionary<string, ModelData>(StringComparer.Ordinal);
-
-			//mxd. Already parsed?
+			// Already parsed?
 			if(!base.AddTextResource(data))
 			{
 				if(clearerrors) ClearError();
@@ -42,72 +58,75 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			while(SkipWhitespace(true)) 
 			{
 				string token = ReadToken();
-				if(!string.IsNullOrEmpty(token)) 
-				{
-					token = StripTokenQuotes(token).ToLowerInvariant();
-					if(token == "model") //model structure start
-					{ 
-						// Find classname
-						SkipWhitespace(true);
-						string displayclassname = StripTokenQuotes(ReadToken(ActorStructure.ACTOR_CLASS_SPECIAL_TOKENS));
-						string classname = displayclassname.ToLowerInvariant();
-
-						if(!string.IsNullOrEmpty(classname) && !entries.ContainsKey(classname)) 
-						{
-							// Now find opening brace
-							if(!NextTokenIs("{")) return false;
+				if(string.IsNullOrEmpty(token)) continue;
 
-							ModeldefStructure mds = new ModeldefStructure();
-							if(mds.Parse(this, displayclassname) && mds.ModelData != null)
-							{
-								entries.Add(classname, mds.ModelData);
-							}
-							
-							if(HasError)
-							{
-								LogError();
-								ClearError();
-							}
+				token = StripTokenQuotes(token).ToLowerInvariant();
+				if(token != "model") continue;
 
-							// Skip untill current structure end
-							if(!mds.ParsingFinished) SkipStructure(1);
-						}
-					} 
-					else 
+				// Find classname
+				SkipWhitespace(true);
+				string classname = ReadToken(ActorStructure.ACTOR_CLASS_SPECIAL_TOKENS);
+				if(string.IsNullOrEmpty(classname))
+				{
+					ReportError("Expected actor class");
+					return false;
+				}
+				
+				// Now find opening brace
+				if(!NextTokenIs("{")) return false;
+
+				// Parse the structure
+				ModeldefStructure mds = new ModeldefStructure();
+				if(mds.Parse(this))
+				{
+					// Fetch Actor info
+					if(actorsbyclass.ContainsKey(classname))
 					{
-						// Unknown structure!
-						if(token != "{")
+						ThingTypeInfo info = General.Map.Data.GetThingInfoEx(actorsbyclass[classname]);
+
+						// Actor has a valid sprite?
+						if(info != null && !string.IsNullOrEmpty(info.Sprite) && !info.Sprite.ToLowerInvariant().StartsWith(DataManager.INTERNAL_PREFIX))
 						{
-							string token2;
-							do
+							string targetsprite = info.Sprite.Substring(0, 5);
+							if(mds.Frames.ContainsKey(targetsprite))
 							{
-								if(!SkipWhitespace(true)) break;
-								token2 = ReadToken();
-								if(string.IsNullOrEmpty(token2)) break;
-							} 
-							while(token2 != "{");
-						}
+								// Create model data
+								ModelData md = new ModelData { InheritActorPitch = mds.InheritActorPitch, InheritActorRoll = mds.InheritActorRoll };
+
+								// Things are complicated in GZDoom...
+								Matrix moffset = Matrix.Translation(mds.Offset.Y, -mds.Offset.X, mds.Offset.Z);
+								Matrix mrotation = Matrix.RotationY(-Angle2D.DegToRad(mds.RollOffset)) * Matrix.RotationX(-Angle2D.DegToRad(mds.PitchOffset)) * Matrix.RotationZ(Angle2D.DegToRad(mds.AngleOffset));
+								md.SetTransform(mrotation, moffset, mds.Scale);
+
+								// Add models
+								foreach(var fs in mds.Frames[targetsprite])
+								{
+									// Texture name will be empty when skin path is embedded in the model
+									string texturename = (!string.IsNullOrEmpty(mds.TextureNames[fs.ModelIndex]) ? mds.TextureNames[fs.ModelIndex].ToLowerInvariant() : string.Empty);
+
+									md.TextureNames.Add(texturename);
+									md.ModelNames.Add(mds.ModelNames[fs.ModelIndex].ToLowerInvariant());
+									md.FrameNames.Add(fs.FrameName);
+									md.FrameIndices.Add(fs.FrameIndex);
+								}
 
-						SkipStructure(1);
+								// Add to collection
+								entries[classname] = md;
+							}
+						}
 					}
 				}
+							
+				if(HasError)
+				{
+					LogError();
+					ClearError();
+				}
 			}
 
-			return entries.Count > 0;
+			return true;
 		}
 
-		// Skips untill current structure end
-		private void SkipStructure(int scopelevel)
-		{
-			do
-			{
-				if(!SkipWhitespace(true)) break;
-				string token = ReadToken();
-				if(string.IsNullOrEmpty(token)) break;
-				if(token == "{") scopelevel++;
-				if(token == "}") scopelevel--;
-			}
-			while(scopelevel > 0);
-		}
+		#endregion
 	}
 }
diff --git a/Source/Core/GZBuilder/GZDoom/ModeldefStructure.cs b/Source/Core/GZBuilder/GZDoom/ModeldefStructure.cs
index 388880c43..a791d212c 100644
--- a/Source/Core/GZBuilder/GZDoom/ModeldefStructure.cs
+++ b/Source/Core/GZBuilder/GZDoom/ModeldefStructure.cs
@@ -1,490 +1,494 @@
 #region ================== Namespaces
 
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Globalization;
 using SlimDX;
 using CodeImp.DoomBuilder.GZBuilder.Data;
-using CodeImp.DoomBuilder.Geometry;
 
 #endregion
 
 namespace CodeImp.DoomBuilder.GZBuilder.GZDoom 
 {
-	internal sealed class ModeldefStructure 
+	internal sealed class ModeldefStructure
 	{
+		#region ================== Constants
+
 		private const int MAX_MODELS = 4; //maximum models per modeldef entry, zero-based
-		private bool parsingfinished;
 
-		internal ModelData ModelData;
-		internal bool ParsingFinished { get { return parsingfinished; } }
+		#endregion
+		
+		#region ================== Structs
 
-		internal bool Parse(ModeldefParser parser, string classname)
+		internal struct FrameStructure
 		{
+			public string SpriteName; // Stays here for HashSet duplicate checks
+			public int ModelIndex;
+			public int FrameIndex;
+			public string FrameName;
+		}
 
-#region ================== Vars
-
-			string[] textureNames = new string[MAX_MODELS];
-			string[] modelNames = new string[MAX_MODELS];
-			string[] frameNames = new string[MAX_MODELS];
-			int[] frameIndices = new int[MAX_MODELS];
-			bool[] modelsUsed = new bool[MAX_MODELS];
-			string path = "";
-			Vector3 scale = new Vector3(1, 1, 1);
-			Vector3 offset = new Vector3();
-			float angleOffset = 0;
-			float pitchOffset = 0;
-			float rollOffset = 0;
-			bool inheritactorpitch = false; 
-			bool inheritactorroll = false;
-
-			string token;
-
-#endregion
-
-			//read modeldef structure contents
-			parsingfinished = false;
-			while(!parsingfinished && parser.SkipWhitespace(true)) 
-			{
-				token = parser.ReadToken();
-				if(!string.IsNullOrEmpty(token)) 
-				{
-					token = parser.StripTokenQuotes(token).ToLowerInvariant(); //ANYTHING can be quoted...
-					switch(token)
-					{
-
-#region ================== Path
-
-						case "path":
-							parser.SkipWhitespace(true);
-							path = parser.StripTokenQuotes(parser.ReadToken(false)).Replace("/", "\\"); // Don't skip newline
-							if(string.IsNullOrEmpty(path))
-							{
-								parser.ReportError("Expected model path");
-								return false;
-							}
-							break;
-
-#endregion
-
-#region ================== Model
-
-						case "model":
-							parser.SkipWhitespace(true);
-
-							//model index
-							int index;
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out index)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected model index, but got \"" + token + "\"");
-								return false;
-							}
-
-							if(index >= MAX_MODELS) 
-							{
-								parser.ReportError("GZDoom doesn't allow more than " + MAX_MODELS + " models per MODELDEF entry");
-								return false;
-							}
-
-							parser.SkipWhitespace(true);
-
-							//model path
-							token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
-							if(string.IsNullOrEmpty(token)) 
-							{
-								parser.ReportError("Expected model name");
-								return false;
-							} 
-
-							//check invalid path chars
-							if(!parser.CheckInvalidPathChars(token)) return false;
-
-							//check extension
-							string modelext = Path.GetExtension(token);
-							if(string.IsNullOrEmpty(modelext)) 
-							{
-								parser.ReportError("Model \"" + token + "\" won't be loaded. Models without extension are not supported by GZDoom");
-								return false;
-							}
-
-							if(modelext != ".md3" && modelext != ".md2") 
-							{
-								parser.ReportError("Model \"" + token + "\" won't be loaded. Only MD2 and MD3 models are supported");
-								return false;
-							}
-
-							//GZDoom allows models with identical modelIndex, it uses the last one encountered
-							modelNames[index] = Path.Combine(path, token);
-							break;
-
-#endregion
-
-#region ================== Skin
-
-						case "skin":
-							parser.SkipWhitespace(true);
-
-							//skin index
-							int skinIndex;
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out skinIndex)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected skin index, but got \"" + token + "\"");
-								return false;
-							}
-
-							if(skinIndex >= MAX_MODELS) 
-							{
-								parser.ReportError("GZDoom doesn't allow more than " + MAX_MODELS + " skins per MODELDEF entry");
-								return false;
-							}
-
-							parser.SkipWhitespace(true);
-
-							//skin path
-							token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
-							if(string.IsNullOrEmpty(token)) 
-							{
-								parser.ReportError("Expected skin path");
-								return false;
-							} 
-
-							//check invalid path chars
-							if(!parser.CheckInvalidPathChars(token)) return false;
-
-							//check extension
-							string texext = Path.GetExtension(token);
-							if(Array.IndexOf(ModelData.SUPPORTED_TEXTURE_EXTENSIONS, texext) == -1) 
-							{
-								parser.ReportError("Image format \"" + texext + "\" is not supported");
-								return false;
-							} 
-
-							//GZDoom allows skins with identical modelIndex, it uses the last one encountered
-							textureNames[skinIndex] = Path.Combine(path, token);
-							break;
-
-#endregion
-
-#region ================== Scale
-
-						case "scale":
-							parser.SkipWhitespace(true);
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!parser.ReadSignedFloat(token, ref scale.Y)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected Scale X value, but got \"" + token + "\"");
-								return false;
-							}
-
-							parser.SkipWhitespace(true);
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!parser.ReadSignedFloat(token, ref scale.X)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected Scale Y value, but got \"" + token + "\"");
-								return false;
-							}
-
-							parser.SkipWhitespace(true);
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!parser.ReadSignedFloat(token, ref scale.Z)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected Scale Z value, but got \"" + token + "\"");
-								return false;
-							}
-							break;
-
-#endregion
-
-#region ================== Offset
-
-						case "offset":
-							parser.SkipWhitespace(true);
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!parser.ReadSignedFloat(token, ref offset.X)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected Offset X value, but got \"" + token + "\"");
-								return false;
-							}
-
-							parser.SkipWhitespace(true);
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!parser.ReadSignedFloat(token, ref offset.Y)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected Offset Y value, but got \"" + token + "\"");
-								return false;
-							}
-
-							parser.SkipWhitespace(true);
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!parser.ReadSignedFloat(token, ref offset.Z)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected Offset Z value, but got \"" + token + "\"");
-								return false;
-							}
-							break;
-
-#endregion
-
-#region ================== ZOffset
-
-						case "zoffset":
-							parser.SkipWhitespace(true);
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!parser.ReadSignedFloat(token, ref offset.Z)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected ZOffset value, but got \"" + token + "\"");
-								return false;
-							}
-							break;
-
-#endregion
-
-#region ================== AngleOffset
+		#endregion
 
-						case "angleoffset":
-							parser.SkipWhitespace(true);
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!parser.ReadSignedFloat(token, ref angleOffset)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected AngleOffset value, but got \"" + token + "\"");
-								return false;
-							}
-							break;
+		#region ================== Variables
 
-#endregion
+		private string[] texturenames;
+		private string[] modelnames;
+		private string path;
+		private Vector3 scale;
+		private Vector3 offset;
+		private float angleoffset;
+		private float pitchoffset;
+		private float rolloffset;
+		private bool inheritactorpitch;
+		private bool inheritactorroll;
 
-#region ================== PitchOffset
+		private Dictionary<string, HashSet<FrameStructure>> frames;
 
-						case "pitchoffset":
-							parser.SkipWhitespace(true);
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!parser.ReadSignedFloat(token, ref pitchOffset)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected PitchOffset value, but got \"" + token + "\"");
-								return false;
-							}
-							break;
+		#endregion
 
-#endregion
+		#region ================== Properties
 
-#region ================== RollOffset
+		public string[] TextureNames { get { return texturenames; } }
+		public string[] ModelNames { get { return modelnames; } }
+		public Vector3 Scale { get { return scale; } }
+		public Vector3 Offset { get { return offset; } }
+		public float AngleOffset { get { return angleoffset; } }
+		public float PitchOffset { get { return pitchoffset; } }
+		public float RollOffset { get { return rolloffset; } }
+		public bool InheritActorPitch { get { return inheritactorpitch; } }
+		public bool InheritActorRoll { get { return inheritactorroll; } }
 
-						case "rolloffset":
-							parser.SkipWhitespace(true);
-							token = parser.StripTokenQuotes(parser.ReadToken());
-							if(!parser.ReadSignedFloat(token, ref rollOffset)) 
-							{
-								// Not numeric!
-								parser.ReportError("Expected RollOffset value, but got \"" + token + "\"");
-								return false;
-							}
-							break;
+		public Dictionary<string, HashSet<FrameStructure>> Frames { get { return frames; } }
 
-#endregion
-
-#region ================== InheritActorPitch
+		#endregion
 
-						case "inheritactorpitch":
-							inheritactorpitch = true;
-							break;
+		#region ================== Constructor
 
-#endregion
-
-#region ================== InheritActorRoll
+		internal ModeldefStructure()
+		{
+			texturenames = new string[MAX_MODELS];
+			modelnames = new string[MAX_MODELS];
+			frames = new Dictionary<string, HashSet<FrameStructure>>(StringComparer.OrdinalIgnoreCase);
+			scale = new Vector3(1.0f, 1.0f, 1.0f);
+		}
 
-						case "inheritactorroll":
-							inheritactorroll = true;
-							break;
+		#endregion
 
-#endregion
+		#region ================== Parsing
 
-#region ================== Frame / FrameIndex
-
-						case "frameindex":
-						case "frame":
-							//parsed all required fields. if got more than one model - find which one(s) should be displayed 
-							if(modelNames.GetLength(0) > 1) 
-							{
-								string spriteLump = null;
-								string spriteFrame = null;
-
-								//step back
-								parser.DataStream.Seek(-token.Length - 1, SeekOrigin.Current);
-
-								//here we check which models are used in first encountered lump and frame
-								while(parser.SkipWhitespace(true)) 
-								{
-									token = parser.StripTokenQuotes(parser.ReadToken()).ToLowerInvariant();
-									if(token == "frameindex" || token == "frame") 
-									{
-										bool frameIndex = (token == "frameindex");
-										parser.SkipWhitespace(true);
-
-										//should be sprite lump
-										token = parser.StripTokenQuotes(parser.ReadToken()).ToLowerInvariant();
-										if(string.IsNullOrEmpty(spriteLump)) 
-										{
-											spriteLump = token;
-										} 
-										else if(spriteLump != token) //got another lump
-										{ 
-											for(int i = 0; i < modelsUsed.Length; i++) 
-											{
-												if(!modelsUsed[i]) 
-												{
-													modelNames[i] = null;
-													textureNames[i] = null;
-												}
-											}
-											break;
-										}
-
-										parser.SkipWhitespace(true);
-
-										//should be sprite frame
-										token = parser.StripTokenQuotes(parser.ReadToken()).ToLowerInvariant();
-										if(string.IsNullOrEmpty(spriteFrame)) 
-										{
-											spriteFrame = token;
-										} 
-										else if(spriteFrame != token) //got another frame
-										{
-											for(int i = 0; i < modelsUsed.Length; i++) 
-											{
-												if(!modelsUsed[i]) 
-												{
-													modelNames[i] = null;
-													textureNames[i] = null;
-												}
-											}
-											break;
-										}
-
-										parser.SkipWhitespace(true);
-
-										//should be model index
-										token = parser.StripTokenQuotes(parser.ReadToken());
-
-										int modelIndex;
-										if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out modelIndex)) 
-										{
-											// Not numeric!
-											parser.ReportError("Expected model index, but got \"" + token + "\"");
-											return false;
-										}
-
-										if(modelIndex >= MAX_MODELS) 
-										{
-											parser.ReportError("GZDoom doesn't allow more than " + MAX_MODELS + " models per MODELDEF entry");
-											return false;
-										}
-
-										if(modelNames[modelIndex] == null) 
-										{
-											parser.ReportError("Model index doesn't correspond to any defined model");
-											return false;
-										}
-
-										modelsUsed[modelIndex] = true;
-
-										parser.SkipWhitespace(true);
-
-										// Should be frame name or index
-										token = parser.StripTokenQuotes(parser.ReadToken());
-										if(frameIndex)
-										{
-											int frame = 0;
-											if(!parser.ReadSignedInt(token, ref frame))
-											{
-												// Not numeric!
-												parser.ReportError("Expected model frame index, but got \"" + token + "\"");
-												return false;
-											}
-
-											// Skip the model if frame index is -1
-											if(frame == -1) modelsUsed[modelIndex] = false;
-											else frameIndices[modelIndex] = frame;
-										}
-										else
-										{
-											if(string.IsNullOrEmpty(token))
-											{
-												// Missing!
-												parser.ReportError("Expected model frame name");
-												return false;
-											}
-
-											frameNames[modelIndex] = token.ToLowerInvariant();
-										}
-									} 
-									else 
-									{
-										//must be "}", step back
-										parser.DataStream.Seek(-token.Length - 1, SeekOrigin.Current);
-										break;
-									}
-								}
-							}
-
-							parsingfinished = true;
-							break;
+		internal bool Parse(ModeldefParser parser)
+		{
+			// Read modeldef structure contents
+			bool parsingfinished = false;
+			while(!parsingfinished && parser.SkipWhitespace(true)) 
+			{
+				string token = parser.ReadToken();
+				if(string.IsNullOrEmpty(token)) continue;
 
-#endregion
-					}
+				switch(token.ToLowerInvariant())
+				{
+					case "path":
+						parser.SkipWhitespace(true);
+						path = parser.StripTokenQuotes(parser.ReadToken(false)).Replace("/", "\\"); // Don't skip newline
+						if(string.IsNullOrEmpty(path))
+						{
+							parser.ReportError("Expected model path");
+							return false;
+						}
+						break;
+
+					case "model":
+						parser.SkipWhitespace(true);
+
+						// Model index
+						int index;
+						token = parser.ReadToken();
+						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out index) || index < 0) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected model index, but got \"" + token + "\"");
+							return false;
+						}
+
+						if(index >= MAX_MODELS) 
+						{
+							parser.ReportError("GZDoom doesn't allow more than " + MAX_MODELS + " models per MODELDEF entry");
+							return false;
+						}
+
+						parser.SkipWhitespace(true);
+
+						// Model path
+						token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
+						if(string.IsNullOrEmpty(token)) 
+						{
+							parser.ReportError("Expected model name");
+							return false;
+						} 
+
+						// Check invalid path chars
+						if(!parser.CheckInvalidPathChars(token)) return false;
+
+						// Check extension
+						string modelext = Path.GetExtension(token);
+						if(string.IsNullOrEmpty(modelext)) 
+						{
+							parser.ReportError("Model \"" + token + "\" won't be loaded. Models without extension are not supported by GZDoom");
+							return false;
+						}
+
+						if(modelext != ".md3" && modelext != ".md2") 
+						{
+							parser.ReportError("Model \"" + token + "\" won't be loaded. Only MD2 and MD3 models are supported");
+							return false;
+						}
+
+						// GZDoom allows models with identical index, it uses the last one encountered
+						modelnames[index] = Path.Combine(path, token);
+						break;
+
+					case "skin":
+						parser.SkipWhitespace(true);
+
+						// Skin index
+						int skinindex;
+						token = parser.ReadToken();
+						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out skinindex) || skinindex < 0) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected skin index, but got \"" + token + "\"");
+							return false;
+						}
+
+						if(skinindex >= MAX_MODELS) 
+						{
+							parser.ReportError("GZDoom doesn't allow more than " + MAX_MODELS + " skins per MODELDEF entry");
+							return false;
+						}
+
+						parser.SkipWhitespace(true);
+
+						// Skin path
+						token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
+						if(string.IsNullOrEmpty(token)) 
+						{
+							parser.ReportError("Expected skin path");
+							return false;
+						} 
+
+						// Check invalid path chars
+						if(!parser.CheckInvalidPathChars(token)) return false;
+
+						// Check extension
+						string texext = Path.GetExtension(token);
+						if(Array.IndexOf(ModelData.SUPPORTED_TEXTURE_EXTENSIONS, texext) == -1) 
+						{
+							parser.ReportError("Image format \"" + texext + "\" is not supported");
+							return false;
+						} 
+
+						// GZDoom allows skins with identical index, it uses the last one encountered
+						texturenames[skinindex] = Path.Combine(path, token);
+						break;
+
+					case "scale":
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(!parser.ReadSignedFloat(token, ref scale.Y)) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected Scale X value, but got \"" + token + "\"");
+							return false;
+						}
+
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(!parser.ReadSignedFloat(token, ref scale.X)) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected Scale Y value, but got \"" + token + "\"");
+							return false;
+						}
+
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(!parser.ReadSignedFloat(token, ref scale.Z)) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected Scale Z value, but got \"" + token + "\"");
+							return false;
+						}
+						break;
+
+					case "offset":
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(!parser.ReadSignedFloat(token, ref offset.X)) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected Offset X value, but got \"" + token + "\"");
+							return false;
+						}
+
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(!parser.ReadSignedFloat(token, ref offset.Y)) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected Offset Y value, but got \"" + token + "\"");
+							return false;
+						}
+
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(!parser.ReadSignedFloat(token, ref offset.Z)) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected Offset Z value, but got \"" + token + "\"");
+							return false;
+						}
+						break;
+
+					case "zoffset":
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(!parser.ReadSignedFloat(token, ref offset.Z)) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected ZOffset value, but got \"" + token + "\"");
+							return false;
+						}
+						break;
+
+					case "angleoffset":
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(!parser.ReadSignedFloat(token, ref angleoffset)) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected AngleOffset value, but got \"" + token + "\"");
+							return false;
+						}
+						break;
+
+					case "pitchoffset":
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(!parser.ReadSignedFloat(token, ref pitchoffset)) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected PitchOffset value, but got \"" + token + "\"");
+							return false;
+						}
+						break;
+
+					case "rolloffset":
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(!parser.ReadSignedFloat(token, ref rolloffset)) 
+						{
+							// Not numeric!
+							parser.ReportError("Expected RollOffset value, but got \"" + token + "\"");
+							return false;
+						}
+						break;
+
+					case "inheritactorpitch": inheritactorpitch = true; break;
+					case "inheritactorroll": inheritactorroll = true; break;
+
+					//FrameIndex <XXXX> <X> <model index> <frame number>
+					case "frameindex":
+						// Sprite name
+						parser.SkipWhitespace(true);
+						string fispritename = parser.ReadToken();
+						if(string.IsNullOrEmpty(fispritename))
+						{
+							parser.ReportError("Expected sprite name");
+							return false;
+						}
+						if(fispritename.Length != 4)
+						{
+							parser.ReportError("Sprite name must be 4 characters long");
+							return false;
+						}
+
+						// Sprite frame
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(string.IsNullOrEmpty(token))
+						{
+							parser.ReportError("Expected sprite frame");
+							return false;
+						}
+						if(token.Length != 1)
+						{
+							parser.ReportError("Sprite frame must be 1 character long");
+							return false;
+						}
+
+						// Make full name
+						fispritename += token;
+
+						// Model index
+						parser.SkipWhitespace(true);
+						int fimodelindnex;
+						token = parser.ReadToken();
+						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out fimodelindnex) || fimodelindnex < 0)
+						{
+							// Not numeric!
+							parser.ReportError("Expected model index, but got \"" + token + "\"");
+							return false;
+						}
+
+						// Frame number
+						parser.SkipWhitespace(true);
+						int fiframeindnex;
+						token = parser.ReadToken();
+						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out fiframeindnex) || fiframeindnex < 0)
+						{
+							// Not numeric!
+							parser.ReportError("Expected frame index, but got \"" + token + "\"");
+							return false;
+						}
+
+						// Add to collection
+						FrameStructure fifs = new FrameStructure { FrameIndex = fiframeindnex, ModelIndex = fimodelindnex, SpriteName = fispritename };
+						if(!frames.ContainsKey(fispritename))
+						{
+							frames.Add(fispritename, new HashSet<FrameStructure>());
+							frames[fispritename].Add(fifs);
+						}
+						else if(frames[fispritename].Contains(fifs))
+						{
+							parser.LogWarning("Duplicate FrameIndex definition");
+						}
+						else
+						{
+							frames[fispritename].Add(fifs);
+						}
+						break;
+
+					//Frame <XXXX> <X> <model index> <"frame name">
+					case "frame":
+						// Sprite name
+						parser.SkipWhitespace(true);
+						string spritename = parser.ReadToken();
+						if(string.IsNullOrEmpty(spritename))
+						{
+							parser.ReportError("Expected sprite name");
+							return false;
+						}
+						if(spritename.Length != 4)
+						{
+							parser.ReportError("Sprite name must be 4 characters long");
+							return false;
+						}
+
+						// Sprite frame
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if(string.IsNullOrEmpty(token))
+						{
+							parser.ReportError("Expected sprite frame");
+							return false;
+						}
+						if(token.Length != 1)
+						{
+							parser.ReportError("Sprite frame must be 1 character long");
+							return false;
+						}
+
+						// Make full name
+						spritename += token;
+
+						// Model index
+						parser.SkipWhitespace(true);
+						int modelindnex;
+						token = parser.ReadToken();
+						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out modelindnex) || modelindnex < 0)
+						{
+							// Not numeric!
+							parser.ReportError("Expected model index, but got \"" + token + "\"");
+							return false;
+						}
+
+						// Frame name
+						parser.SkipWhitespace(true);
+						string framename = parser.StripTokenQuotes(parser.ReadToken());
+						if(string.IsNullOrEmpty(framename))
+						{
+							parser.ReportError("Expected frame name");
+							return false;
+						}
+
+						// Add to collection
+						FrameStructure fs = new FrameStructure { FrameName = framename, ModelIndex = modelindnex, SpriteName = spritename };
+						if(!frames.ContainsKey(spritename))
+						{
+							frames.Add(spritename, new HashSet<FrameStructure>());
+							frames[spritename].Add(fs);
+						}
+						else if(frames[spritename].Contains(fs))
+						{
+							parser.LogWarning("Duplicate Frame definition");
+						}
+						else
+						{
+							frames[spritename].Add(fs);
+						}
+						break;
+
+					case "{":
+						parser.ReportError("Unexpected scope start");
+						return false;
+
+					// Structure ends here
+					case "}":
+						parsingfinished = true;
+						break;
 				}
 			}
 
-			// Find closing brace, then quit
-			while(parser.SkipWhitespace(true)) 
+			// Perform some integrity checks
+			if(!parsingfinished)
 			{
-				token = parser.ReadToken();
-				if(string.IsNullOrEmpty(token) || token == "}") break;
-			}
-
-			// Bail out when got errors or no models are used
-			if(Array.IndexOf(modelsUsed, true) == -1)
-			{
-				parser.ReportError("No models are used by \"" + classname + "\"");
+				parser.ReportError("Unclosed structure scope");
 				return false;
 			}
-			
-			// Classname is set in ModeldefParser
-			ModelData = new ModelData { InheritActorPitch = inheritactorpitch, InheritActorRoll = inheritactorroll };
-			Matrix moffset = Matrix.Translation(offset.Y, -offset.X, offset.Z); // Things are complicated in GZDoom...
-			Matrix mrotation = Matrix.RotationY(-Angle2D.DegToRad(rollOffset)) * Matrix.RotationX(-Angle2D.DegToRad(pitchOffset)) * Matrix.RotationZ(Angle2D.DegToRad(angleOffset));
-			ModelData.SetTransform(mrotation, moffset, scale);
-
-			for(int i = 0; i < modelNames.Length; i++) 
+
+			// Any models defined?
+			bool valid = false;
+			for(int i = 0; i < modelnames.Length; i++)
 			{
-				if(!string.IsNullOrEmpty(modelNames[i]) && modelsUsed[i]) 
+				if(!string.IsNullOrEmpty(modelnames[i]))
 				{
-					ModelData.TextureNames.Add(string.IsNullOrEmpty(textureNames[i]) ? string.Empty : textureNames[i].ToLowerInvariant());
-					ModelData.ModelNames.Add(modelNames[i].ToLowerInvariant());
-					ModelData.FrameNames.Add(frameNames[i]);
-					ModelData.FrameIndices.Add(frameIndices[i]);
+					//INFO: skin may be defined in the model itself, so we don't check it here
+					valid = true;
+					break;
 				}
 			}
 
-			if(ModelData.ModelNames.Count == 0)
+			if(!valid)
 			{
-				parser.ReportError("\"" + classname + "\" has no models");
+				parser.ReportError("Structure doesn't define any models");
 				return false;
 			}
 
+			// Check skin-model associations
+			for(int i = 0; i < texturenames.Length; i++)
+			{
+				if(!string.IsNullOrEmpty(texturenames[i]) && string.IsNullOrEmpty(modelnames[i]))
+				{
+					parser.ReportError("No model is defined for skin " + i + ":\"" + texturenames[i] + "\"");
+					return false;
+				}
+			}
+
 			return true;
 		}
+
+		#endregion
 	}
 }
diff --git a/Source/Core/General/Launcher.cs b/Source/Core/General/Launcher.cs
index 2ed4c6cd6..80bc594da 100644
--- a/Source/Core/General/Launcher.cs
+++ b/Source/Core/General/Launcher.cs
@@ -375,11 +375,11 @@ namespace CodeImp.DoomBuilder
 					General.Map.Graphics.Reset();
 					General.MainWindow.RedrawDisplay();
 				}
-				else if(General.Editing.Mode is VisualMode)
+				/*else if(General.Editing.Mode is VisualMode)
 				{
-					//General.MainWindow.StopExclusiveMouseInput();
-					//General.MainWindow.StartExclusiveMouseInput();
-				}
+					General.MainWindow.StopExclusiveMouseInput();
+					General.MainWindow.StartExclusiveMouseInput();
+				}*/
 			}
 
 			General.MainWindow.FocusDisplay();
diff --git a/Source/Core/Rendering/Renderer2D.cs b/Source/Core/Rendering/Renderer2D.cs
index 4076670c4..ef753b97e 100644
--- a/Source/Core/Rendering/Renderer2D.cs
+++ b/Source/Core/Rendering/Renderer2D.cs
@@ -1615,7 +1615,7 @@ namespace CodeImp.DoomBuilder.Rendering
 			graphics.Device.SetRenderState(RenderState.FogEnable, false);
 			graphics.Shaders.Display2D.Texture1 = label.Texture;
 			SetWorldTransformation(false);
-			graphics.Shaders.Display2D.SetSettings(1f, 1f, 0f, 1f, true);
+			graphics.Shaders.Display2D.SetSettings(1f, 1f, 0f, 1f, false);
 			graphics.Device.SetStreamSource(0, label.VertexBuffer, 0, FlatVertex.Stride);
 
 			// Draw
@@ -1648,7 +1648,7 @@ namespace CodeImp.DoomBuilder.Rendering
 			graphics.Device.SetRenderState(RenderState.TextureFactor, -1);
 			graphics.Device.SetRenderState(RenderState.FogEnable, false);
 			SetWorldTransformation(false);
-			graphics.Shaders.Display2D.SetSettings(1f, 1f, 0f, 1f, true);
+			graphics.Shaders.Display2D.SetSettings(1f, 1f, 0f, 1f, false);
 			
 			// Begin drawing
 			graphics.Shaders.Display2D.Begin();
diff --git a/Source/Core/Rendering/TextLabel.cs b/Source/Core/Rendering/TextLabel.cs
index 26f35fcd2..7f75619c8 100644
--- a/Source/Core/Rendering/TextLabel.cs
+++ b/Source/Core/Rendering/TextLabel.cs
@@ -88,7 +88,7 @@ namespace CodeImp.DoomBuilder.Rendering
 		public TextAlignmentX AlignX { get { return alignx; } set { alignx = value; updateneeded = true; } }
 		public TextAlignmentY AlignY { get { return aligny; } set { aligny = value; updateneeded = true; } }
 		public PixelColor Color { get { return color; } set { if(!color.Equals(value)) { color = value; textureupdateneeded = true; } } }
-		public PixelColor Backcolor { get { return backcolor; } set { if(!backcolor.Equals(value)) { backcolor = value; textureupdateneeded = true; } } }
+		public PixelColor BackColor { get { return backcolor; } set { if(!backcolor.Equals(value)) { backcolor = value; textureupdateneeded = true; } } }
 		public bool DrawBackground { get { return drawbg; } set { if(drawbg != value) { drawbg = value; textureupdateneeded = true; } } } //mxd
 		internal Texture Texture { get { return texture; } } //mxd
 		internal VertexBuffer VertexBuffer { get { return textbuffer; } }
@@ -109,7 +109,7 @@ namespace CodeImp.DoomBuilder.Rendering
 			this.font = General.Settings.TextLabelFont; //mxd
 			this.rect = new RectangleF(0f, 0f, 1f, 1f);
 			this.color = new PixelColor(255, 255, 255, 255);
-			this.backcolor = new PixelColor(255, 0, 0, 0);
+			this.backcolor = new PixelColor(128, 0, 0, 0);
 			this.alignx = TextAlignmentX.Center;
 			this.aligny = TextAlignmentY.Top;
 			this.textsize = new SizeF();
@@ -118,10 +118,6 @@ namespace CodeImp.DoomBuilder.Rendering
 			
 			// Register as resource
 			General.Map.Graphics.RegisterResource(this);
-
-			//mxd. Create the buffer
-			this.textbuffer = new VertexBuffer(General.Map.Graphics.Device, 4 * FlatVertex.Stride,
-										  Usage.Dynamic | Usage.WriteOnly, VertexFormat.None, Pool.Default);
 			
 			// We have no destructor
 			GC.SuppressFinalize(this);
@@ -203,7 +199,7 @@ namespace CodeImp.DoomBuilder.Rendering
 						}
 
 						// Create label image
-						Bitmap img = CreateLabelImage(text, font, color, backcolor, drawbg);
+						Bitmap img = CreateLabelImage(text, font, alignx, aligny, color, backcolor, drawbg);
 						textsize = img.Size;
 
 						// Create texture
@@ -234,6 +230,13 @@ namespace CodeImp.DoomBuilder.Rendering
 						case TextAlignmentY.Bottom: beginy = absview.Y + absview.Height - textsize.Height; break;
 					}
 
+					//mxd. Create the buffer
+					if(textbuffer == null || textbuffer.Disposed)
+					{
+						textbuffer = new VertexBuffer(General.Map.Graphics.Device, 4 * FlatVertex.Stride,
+												  Usage.Dynamic | Usage.WriteOnly, VertexFormat.None, Pool.Default);
+					}
+
 					//mxd. Lock the buffer
 					using(DataStream stream = textbuffer.Lock(0, 4 * FlatVertex.Stride, LockFlags.Discard | LockFlags.NoSystemLock))
 					{
@@ -258,7 +261,7 @@ namespace CodeImp.DoomBuilder.Rendering
 		}
 
 		//mxd
-		private static Bitmap CreateLabelImage(string text, Font font, PixelColor color, PixelColor backcolor, bool drawbg)
+		private static Bitmap CreateLabelImage(string text, Font font, TextAlignmentX alignx, TextAlignmentY aligny, PixelColor color, PixelColor backcolor, bool drawbg)
 		{
 			PointF textorigin = new PointF(4, 3);
 			RectangleF textrect = new RectangleF(textorigin, General.Interface.MeasureString(text, font));
@@ -266,7 +269,25 @@ namespace CodeImp.DoomBuilder.Rendering
 			textrect.Height = (float)Math.Round(textrect.Height);
 			RectangleF bgrect = new RectangleF(0, 0, textrect.Width + textorigin.X * 2, textrect.Height + textorigin.Y * 2);
 
-			Bitmap result = new Bitmap((int)bgrect.Width, (int)bgrect.Height);
+			// Make PO2 image, for speed and giggles...
+			RectangleF po2rect = new RectangleF(0, 0, General.NextPowerOf2((int)bgrect.Width), General.NextPowerOf2((int)bgrect.Height));
+
+			switch(alignx)
+			{
+				case TextAlignmentX.Center: bgrect.X = (po2rect.Width - bgrect.Width) / 2; break;
+				case TextAlignmentX.Right:  bgrect.X = po2rect.Width - bgrect.Width; break;
+			}
+
+			switch(aligny)
+			{
+				case TextAlignmentY.Middle: bgrect.Y = (po2rect.Height - bgrect.Height) / 2; break;
+				case TextAlignmentY.Bottom: bgrect.Y = po2rect.Height - bgrect.Height; break;
+			}
+
+			textrect.X += bgrect.X;
+			textrect.Y += bgrect.Y;
+
+			Bitmap result = new Bitmap((int)po2rect.Width, (int)po2rect.Height);
 			using(Graphics g = Graphics.FromImage(result))
 			{
 				g.SmoothingMode = SmoothingMode.HighQuality;
@@ -276,7 +297,7 @@ namespace CodeImp.DoomBuilder.Rendering
 				// Draw text
 				using(StringFormat sf = new StringFormat())
 				{
-					sf.FormatFlags = StringFormatFlags.NoWrap;
+					sf.FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.NoWrap;
 					sf.Alignment = StringAlignment.Center;
 					sf.LineAlignment = StringAlignment.Center;
 
@@ -319,21 +340,20 @@ namespace CodeImp.DoomBuilder.Rendering
 						using(SolidBrush brush = new SolidBrush(backcolor.ToColor()))
 							g.DrawString(text, font, brush, textrect, sf);
 					}
-					// Draw text with outline 
+					// Draw plain text
 					else
 					{
-						RectangleF pathrect = textrect;
-						pathrect.Inflate(1, 3);
+						RectangleF plainbgrect = textrect;
+						if(text.Length > 1) plainbgrect.Inflate(6, 2);
 
-						GraphicsPath p = new GraphicsPath();
-						p.AddString(text, font.FontFamily, (int)font.Style, g.DpiY * font.Size / 72f, pathrect, sf);
+						RectangleF plaintextrect = textrect;
+						plaintextrect.Inflate(6, 4);
 
-						// Draw'n'fill text
-						using(Pen pen = new Pen(backcolor.ToColor(), 3))
-							g.DrawPath(pen, p);
+						using(SolidBrush brush = new SolidBrush(backcolor.ToColor()))
+							g.FillRectangle(brush, plainbgrect);
 
 						using(SolidBrush brush = new SolidBrush(color.ToColor()))
-							g.FillPath(brush, p);
+							g.DrawString(text, font, brush, plaintextrect, sf);
 					}
 				}
 			}
diff --git a/Source/Core/Windows/MainForm.cs b/Source/Core/Windows/MainForm.cs
index 519617db8..2cb68c50d 100644
--- a/Source/Core/Windows/MainForm.cs
+++ b/Source/Core/Windows/MainForm.cs
@@ -204,16 +204,19 @@ namespace CodeImp.DoomBuilder.Windows
 		// Constructor
 		internal MainForm()
 		{
+			// Fetch pointer
+			windowptr = base.Handle;
+			
+			//mxd. Graphics
+			graphics = Graphics.FromHwndInternal(windowptr);
+			
 			//mxd. Set DPI-aware icon size
-			using(Graphics g = this.CreateGraphics()) 
-			{
-				DPIScaler = new SizeF(g.DpiX / 96, g.DpiY / 96);
+			DPIScaler = new SizeF(graphics.DpiX / 96, graphics.DpiY / 96);
 
-				if(DPIScaler.Width != 1.0f || DPIScaler.Height != 1.0f)
-				{
-					ScaledIconSize.Width = (int)Math.Round(ScaledIconSize.Width * DPIScaler.Width);
-					ScaledIconSize.Height = (int)Math.Round(ScaledIconSize.Height * DPIScaler.Height);
-				}
+			if(DPIScaler.Width != 1.0f || DPIScaler.Height != 1.0f)
+			{
+				ScaledIconSize.Width = (int)Math.Round(ScaledIconSize.Width * DPIScaler.Width);
+				ScaledIconSize.Height = (int)Math.Round(ScaledIconSize.Height * DPIScaler.Height);
 			}
 			
 			// Setup controls
@@ -234,9 +237,6 @@ namespace CodeImp.DoomBuilder.Windows
 			labelcollapsedinfo.Text = "";
 			display.Dock = DockStyle.Fill;
 			
-			// Fetch pointer
-			windowptr = base.Handle;
-			
 			// Make array for view modes
 			viewmodesbuttons = new ToolStripButton[Renderer2D.NUM_VIEW_MODES];
 			viewmodesbuttons[(int)ViewMode.Normal] = buttonviewnormal;
@@ -290,9 +290,6 @@ namespace CodeImp.DoomBuilder.Windows
 			//mxd. Hints
 			hintsPanel = new HintsPanel();
 			hintsDocker = new Docker("hints", "Help", hintsPanel);
-
-			//mxd. Graphics
-			graphics = Graphics.FromHwndInternal(windowptr);
 		}
 		
 		#endregion
@@ -2679,14 +2676,14 @@ namespace CodeImp.DoomBuilder.Windows
 		private string GetDisplayFilename(string filename)
 		{
 			// String doesnt fit?
-			if(GetStringWidth(filename) > MAX_RECENT_FILES_PIXELS)
+			if(MeasureString(filename, this.Font).Width > MAX_RECENT_FILES_PIXELS)
 			{
 				// Start chopping off characters
 				for(int i = filename.Length - 6; i >= 0; i--)
 				{
 					// Does it fit now?
 					string newname = filename.Substring(0, 3) + "..." + filename.Substring(filename.Length - i, i);
-					if(GetStringWidth(newname) <= MAX_RECENT_FILES_PIXELS) return newname;
+					if(MeasureString(newname, this.Font).Width <= MAX_RECENT_FILES_PIXELS) return newname;
 				}
 
 				// Cant find anything that fits (most unlikely!)
@@ -2699,14 +2696,6 @@ namespace CodeImp.DoomBuilder.Windows
 			}
 		}
 		
-		// This returns the width of a string
-		private float GetStringWidth(string str)
-		{
-			Graphics g = Graphics.FromHwndInternal(this.Handle);
-			SizeF strsize = g.MeasureString(str, this.Font);
-			return strsize.Width;
-		}
-		
 		// Exit clicked
 		private void itemexit_Click(object sender, EventArgs e) { this.Close(); }
 
diff --git a/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs
index 8a653f9e6..ec077bf02 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs
@@ -395,7 +395,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					l.AlignX = TextAlignmentX.Center;
 					l.AlignY = TextAlignmentY.Middle;
 					l.Color = General.Colors.InfoLine;
-					l.Backcolor = General.Colors.Background.WithAlpha(255);
+					l.BackColor = General.Colors.Background.WithAlpha(128);
 					larr[i] = l;
 				}
 
@@ -430,7 +430,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				l.AlignX = TextAlignmentX.Center;
 				l.AlignY = TextAlignmentY.Middle;
 				l.Color = (linedef == highlighted ? General.Colors.Selection : General.Colors.Highlight);
-				l.Backcolor = General.Colors.Background.WithAlpha(255);
+				l.BackColor = General.Colors.Background.WithAlpha(192);
 				l.Text = (++index).ToString();
 				labels.Add(linedef, l);
 			}
diff --git a/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs
index 6ddd12777..fe826fcae 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs
@@ -160,7 +160,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					labelarray[i].AlignX = TextAlignmentX.Center;
 					labelarray[i].AlignY = TextAlignmentY.Middle;
 					labelarray[i].Color = c;
-					labelarray[i].Backcolor = General.Colors.Background.WithAlpha(255);
+					labelarray[i].BackColor = General.Colors.Background.WithAlpha(128);
 				}
 				labels.Add(s, labelarray);
 			}
diff --git a/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
index 292525314..ee9614437 100644
--- a/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
@@ -936,7 +936,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					l.AlignX = TextAlignmentX.Center;
 					l.AlignY = TextAlignmentY.Middle;
 					l.Color = General.Colors.InfoLine;
-					l.Backcolor = General.Colors.Background.WithAlpha(255);
+					l.BackColor = General.Colors.Background.WithAlpha(128);
 					larr[i] = l;
 				}
 
@@ -982,8 +982,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				}
 				
 				l.Color = (thing == highlighted ? General.Colors.Selection : General.Colors.Highlight);
-				l.Backcolor = General.Colors.Background.WithAlpha(255);
-				l.DrawBackground = true;
+				l.BackColor = General.Colors.Background.WithAlpha(192);
 				l.Text = (++index).ToString();
 				labels.Add(thing, l);
 			}
diff --git a/Source/Plugins/BuilderModes/General/LineLengthLabel.cs b/Source/Plugins/BuilderModes/General/LineLengthLabel.cs
index 575e99c2d..0ae3dd16d 100644
--- a/Source/Plugins/BuilderModes/General/LineLengthLabel.cs
+++ b/Source/Plugins/BuilderModes/General/LineLengthLabel.cs
@@ -103,12 +103,14 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		// Initialization
 		private void Initialize()
 		{
-			label = new TextLabel();
-			label.AlignX = TextAlignmentX.Center;
-			label.AlignY = TextAlignmentY.Middle;
-			label.Color = General.Colors.Highlight;
-			label.Backcolor = General.Colors.Background;
-			label.TransformCoords = true;
+			label = new TextLabel
+					{
+						AlignX = TextAlignmentX.Center, 
+						AlignY = TextAlignmentY.Middle, 
+						Color = General.Colors.Highlight, 
+						BackColor = General.Colors.Background.WithAlpha(64), 
+						TransformCoords = true,
+					};
 		}
 		
 		// Disposer
-- 
GitLab