From 66f75ec7c46248f4a1c5db7d6ece6ffbad574b06 Mon Sep 17 00:00:00 2001
From: MaxED <j.maxed@gmail.com>
Date: Thu, 13 Nov 2014 09:43:39 +0000
Subject: [PATCH] Internal: added a debug console.

---
 Source/Core/Builder.csproj                    |  12 +
 Source/Core/Controls/DebugConsole.cs          | 259 +++++++++++++++++
 Source/Core/Controls/DebugConsole.designer.cs | 263 ++++++++++++++++++
 Source/Core/Controls/DebugConsole.resx        | 123 ++++++++
 Source/Core/General/General.cs                |   6 +-
 Source/Core/General/MurmurHash2.cs            | 109 ++++++++
 Source/Core/Properties/Resources.Designer.cs  |  14 +
 Source/Core/Properties/Resources.resx         |  46 +--
 Source/Core/Resources/Clear.png               | Bin 0 -> 706 bytes
 Source/Core/Resources/WordWrap.png            | Bin 0 -> 1148 bytes
 Source/Core/VisualModes/VisualMode.cs         |  40 ++-
 Source/Core/Windows/MainForm.Designer.cs      |  17 +-
 Source/Core/Windows/MainForm.cs               |  26 ++
 .../VisualModes/BaseVisualMode.cs             |   6 +-
 14 files changed, 880 insertions(+), 41 deletions(-)
 create mode 100644 Source/Core/Controls/DebugConsole.cs
 create mode 100644 Source/Core/Controls/DebugConsole.designer.cs
 create mode 100644 Source/Core/Controls/DebugConsole.resx
 create mode 100644 Source/Core/General/MurmurHash2.cs
 create mode 100644 Source/Core/Resources/Clear.png
 create mode 100644 Source/Core/Resources/WordWrap.png

diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj
index 5602aa3b9..bc2e52644 100644
--- a/Source/Core/Builder.csproj
+++ b/Source/Core/Builder.csproj
@@ -683,6 +683,12 @@
     <Compile Include="Config\PasteOptions.cs" />
     <Compile Include="Config\ThingsFlagsCompare.cs" />
     <Compile Include="Controls\ButtonsNumericTextboxDesigner.cs" />
+    <Compile Include="Controls\DebugConsole.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="Controls\DebugConsole.designer.cs">
+      <DependentUpon>DebugConsole.cs</DependentUpon>
+    </Compile>
     <Compile Include="Controls\Docker.cs" />
     <Compile Include="Controls\DockersControl.cs">
       <SubType>UserControl</SubType>
@@ -746,6 +752,7 @@
     <Compile Include="General\CRC.cs" />
     <Compile Include="General\ErrorItem.cs" />
     <Compile Include="General\ErrorLogger.cs" />
+    <Compile Include="General\MurmurHash2.cs" />
     <Compile Include="General\SavePurpose.cs" />
     <Compile Include="Geometry\CurveTools.cs" />
     <Compile Include="GZBuilder\Controls\AngleControl.cs">
@@ -936,6 +943,9 @@
     <Compile Include="ZDoom\DecorateParser.cs" />
     <Compile Include="ZDoom\StateStructure.cs" />
     <Compile Include="Editing\EditingManager.cs" />
+    <EmbeddedResource Include="Controls\DebugConsole.resx">
+      <DependentUpon>DebugConsole.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="Controls\DockersControl.resx">
       <DependentUpon>DockersControl.cs</DependentUpon>
     </EmbeddedResource>
@@ -996,6 +1006,7 @@
     <None Include="Resources\ClearTextures.png" />
     <None Include="Resources\Cursor.png" />
     <None Include="Resources\Brightness.png" />
+    <None Include="Resources\Clear.png" />
     <Content Include="Resources\DB2.ico" />
     <None Include="Resources\GZDB2.ico" />
     <None Include="Resources\fog.png" />
@@ -1018,6 +1029,7 @@
     <Content Include="Resources\Model.png" />
     <None Include="Resources\ModelDisabled.png" />
     <Content Include="Resources\Model_selected.png" />
+    <None Include="Resources\WordWrap.png" />
     <None Include="Resources\ThingCategory.png" />
     <None Include="Resources\ScreenshotActiveWindow.png" />
     <None Include="Resources\Screenshot.png" />
diff --git a/Source/Core/Controls/DebugConsole.cs b/Source/Core/Controls/DebugConsole.cs
new file mode 100644
index 000000000..3c19ad511
--- /dev/null
+++ b/Source/Core/Controls/DebugConsole.cs
@@ -0,0 +1,259 @@
+#region ================== Namespaces
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Windows.Forms;
+
+#endregion
+
+namespace CodeImp.DoomBuilder
+{
+	#region ================== Enums
+
+	[Flags]
+	public enum DebugMessageType
+	{
+		Log = 1,
+		Info = 2,
+		Warning = 4,
+		Error = 8,
+		Special = 16,
+	}
+
+	#endregion
+
+	public partial class DebugConsole : UserControl
+	{
+		#region ================== Variables
+
+		private static readonly List<KeyValuePair<DebugMessageType, string>> messages = new List<KeyValuePair<DebugMessageType, string>>(1000);
+		private DebugMessageType filters;
+
+		//Colors
+		private readonly Dictionary<DebugMessageType, Color> textcolors;
+		private readonly Dictionary<DebugMessageType, string> textheaders;
+
+		private static int charcount;
+		private const int MAX_CHARS = short.MaxValue;
+		private static long starttime = -1;
+		private static DebugConsole me;
+
+		#endregion
+
+		#region ================== Properties
+
+		public bool AlwaysOnTop { get { return alwaysontop.Checked; } }
+
+		#endregion
+
+		#region ================== Constructor
+
+		public DebugConsole() 
+		{
+			InitializeComponent();
+			me = this;
+
+			// Setup filters
+			foreach (ToolStripMenuItem item in filterselector.DropDownItems)
+			{
+				UpdateFilters(item);
+			}
+
+			// Setup colors
+			textcolors = new Dictionary<DebugMessageType, Color> {
+				             { DebugMessageType.Log, SystemColors.WindowText }, 
+							 { DebugMessageType.Info, Color.DarkGreen }, 
+							 { DebugMessageType.Warning, Color.DarkOrange }, 
+							 { DebugMessageType.Error, Color.DarkRed }, 
+							 { DebugMessageType.Special, Color.DarkMagenta }
+			             };
+
+			// Setup headers
+			textheaders = new Dictionary<DebugMessageType, string> {
+				              { DebugMessageType.Log, string.Empty}, 
+							  { DebugMessageType.Info, string.Empty}, 
+							  { DebugMessageType.Warning, "Warning: "}, 
+							  { DebugMessageType.Error, "ERROR: "}, 
+							  { DebugMessageType.Special, string.Empty}
+			              };
+
+			// Word wrap?
+			wordwrap.Checked = console.WordWrap;
+
+			// Pending messages?
+			if (messages.Count > 0) UpdateMessages();
+		}
+
+		#endregion
+
+		#region ================== Methods
+
+		public static void Write(string text)
+		{
+			Write(DebugMessageType.Info, text);
+		}
+
+		public static void WriteLine(string text) 
+		{
+			Write(DebugMessageType.Info, text + Environment.NewLine);
+		}
+
+		public static void Write(DebugMessageType type, string text)
+		{
+			if(me != null && me.InvokeRequired) 
+			{
+				me.Invoke(new Action<DebugMessageType, string>(Write), new object[] { type, text });
+			} 
+			else 
+			{
+				messages.Add(new KeyValuePair<DebugMessageType, string>(type, text));
+				if(me != null && (me.filters & type) == type) 
+				{
+					me.AddMessage(type, text, true);
+				}
+			}
+		}
+
+		public static void WriteLine(DebugMessageType type, string text)
+		{
+			Write(type, text + Environment.NewLine);
+		}
+
+		public static void Clear()
+		{
+			if (me != null && me.InvokeRequired)
+			{
+				me.Invoke(new Action(Clear));
+			}
+			else
+			{
+				if (me != null) me.console.Clear();
+				messages.Clear();
+				charcount = 0;
+			}
+		}
+
+		public static void StartTimer() 
+		{
+			starttime = SlimDX.Configuration.Timer.ElapsedMilliseconds;
+		}
+
+		public static void StopTimer(string message) 
+		{
+			if (starttime == -1) 
+			{
+				Write(DebugMessageType.Warning, "Call General.Console.StartTimer before General.Console.StopTimer!");
+			}
+			else 
+			{
+				long endtime = SlimDX.Configuration.Timer.ElapsedMilliseconds;
+				
+				if (message.Contains("%"))
+					message = message.Replace("&", (endtime - starttime) + " ms.");
+				else 
+					message = message.TrimEnd() + " " + (endtime - starttime) + " ms.";
+
+				Write(DebugMessageType.Special, message);
+			}
+
+			starttime = -1;
+		}
+
+		private void AddMessage(DebugMessageType type, string text, bool scroll)
+		{
+			text = textheaders[type] + text;
+			bool updatemessages = false;
+
+			while (charcount + text.Length > MAX_CHARS) 
+			{
+				charcount -= messages[0].Value.Length;
+				messages.RemoveAt(0);
+				updatemessages = true;
+			}
+
+			if(updatemessages) UpdateMessages();
+			charcount += text.Length; 
+
+			console.SelectionStart = console.TextLength;
+			console.SelectionColor = textcolors[type];
+			console.AppendText(text);
+			if(scroll && autoscroll.Checked) console.ScrollToCaret();
+		}
+
+		private void UpdateFilters(ToolStripMenuItem item)
+		{
+			DebugMessageType flag = (DebugMessageType)(int)item.Tag;
+			if(item.Checked) 
+			{
+				filters |= flag;
+			} 
+			else 
+			{
+				filters &= ~flag;
+			}
+		}
+
+		private void UpdateMessages()
+		{
+			console.Clear();
+			charcount = 0;
+
+			console.SuspendLayout();
+			foreach (KeyValuePair<DebugMessageType, string> pair in messages)
+			{
+				if((filters & pair.Key) == pair.Key && CheckTextFilter(pair.Value, searchbox.Text))
+				{
+					AddMessage(pair.Key, pair.Value, false);
+				}
+			}
+
+			console.ResumeLayout();
+			console.ScrollToCaret();
+		}
+
+		// Should we display this message?
+		private bool CheckTextFilter(string text, string filter) 
+		{
+			if (string.IsNullOrEmpty(filter) || filter.Length < 3) return true;
+			return text.ToUpperInvariant().Contains(filter.ToUpperInvariant());
+		}
+
+		#endregion
+
+		#region ================== Events
+
+		private void clearall_Click(object sender, EventArgs e) 
+		{
+			Clear();
+		}
+
+		private void filters_Click(object sender, EventArgs e)
+		{
+			UpdateFilters(sender as ToolStripMenuItem);
+			UpdateMessages();
+		}
+
+		private void wordwrap_Click(object sender, EventArgs e) 
+		{
+			console.WordWrap = wordwrap.Checked;
+		}
+
+		#endregion
+
+		#region ================== Search events
+
+		private void searchclear_Click(object sender, EventArgs e) 
+		{
+			searchbox.Clear();
+		}
+
+		private void searchbox_TextChanged(object sender, EventArgs e) 
+		{
+			if(string.IsNullOrEmpty(searchbox.Text) || searchbox.Text.Length > 2) UpdateMessages();
+		}
+
+		#endregion
+
+	}
+}
diff --git a/Source/Core/Controls/DebugConsole.designer.cs b/Source/Core/Controls/DebugConsole.designer.cs
new file mode 100644
index 000000000..55fedebde
--- /dev/null
+++ b/Source/Core/Controls/DebugConsole.designer.cs
@@ -0,0 +1,263 @@
+namespace CodeImp.DoomBuilder
+{
+	partial class DebugConsole
+	{
+		/// <summary> 
+		/// Required designer variable.
+		/// </summary>
+		private System.ComponentModel.IContainer components = null;
+
+		/// <summary> 
+		/// Clean up any resources being used.
+		/// </summary>
+		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+		protected override void Dispose(bool disposing) {
+			if(disposing && (components != null)) {
+				components.Dispose();
+			}
+			base.Dispose(disposing);
+		}
+
+		#region Component Designer generated code
+
+		/// <summary> 
+		/// Required method for Designer support - do not modify 
+		/// the contents of this method with the code editor.
+		/// </summary>
+		private void InitializeComponent() {
+			this.toolStrip1 = new System.Windows.Forms.ToolStrip();
+			this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator();
+			this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
+			this.toolStripLabel1 = new System.Windows.Forms.ToolStripLabel();
+			this.searchbox = new System.Windows.Forms.ToolStripTextBox();
+			this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
+			this.autoscroll = new CodeImp.DoomBuilder.Controls.ToolStripCheckBox();
+			this.console = new System.Windows.Forms.RichTextBox();
+			this.clearall = new System.Windows.Forms.ToolStripButton();
+			this.wordwrap = new System.Windows.Forms.ToolStripButton();
+			this.filterselector = new System.Windows.Forms.ToolStripDropDownButton();
+			this.filterlog = new System.Windows.Forms.ToolStripMenuItem();
+			this.filterinfo = new System.Windows.Forms.ToolStripMenuItem();
+			this.filterwarning = new System.Windows.Forms.ToolStripMenuItem();
+			this.filtererrors = new System.Windows.Forms.ToolStripMenuItem();
+			this.filterspecial = new System.Windows.Forms.ToolStripMenuItem();
+			this.searchclear = new System.Windows.Forms.ToolStripButton();
+			this.alwaysontop = new System.Windows.Forms.ToolStripButton();
+			this.toolStrip1.SuspendLayout();
+			this.SuspendLayout();
+			// 
+			// toolStrip1
+			// 
+			this.toolStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
+			this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.clearall,
+            this.toolStripSeparator3,
+            this.wordwrap,
+            this.filterselector,
+            this.toolStripSeparator1,
+            this.toolStripLabel1,
+            this.searchbox,
+            this.searchclear,
+            this.toolStripSeparator2,
+            this.alwaysontop,
+            this.autoscroll});
+			this.toolStrip1.Location = new System.Drawing.Point(0, 0);
+			this.toolStrip1.Name = "toolStrip1";
+			this.toolStrip1.Size = new System.Drawing.Size(800, 25);
+			this.toolStrip1.TabIndex = 0;
+			this.toolStrip1.Text = "toolStrip1";
+			// 
+			// toolStripSeparator3
+			// 
+			this.toolStripSeparator3.Name = "toolStripSeparator3";
+			this.toolStripSeparator3.Size = new System.Drawing.Size(6, 25);
+			// 
+			// toolStripSeparator1
+			// 
+			this.toolStripSeparator1.Name = "toolStripSeparator1";
+			this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25);
+			// 
+			// toolStripLabel1
+			// 
+			this.toolStripLabel1.Name = "toolStripLabel1";
+			this.toolStripLabel1.Size = new System.Drawing.Size(39, 22);
+			this.toolStripLabel1.Text = "Filter: ";
+			// 
+			// searchbox
+			// 
+			this.searchbox.Name = "searchbox";
+			this.searchbox.Size = new System.Drawing.Size(100, 25);
+			this.searchbox.TextChanged += new System.EventHandler(this.searchbox_TextChanged);
+			// 
+			// toolStripSeparator2
+			// 
+			this.toolStripSeparator2.Name = "toolStripSeparator2";
+			this.toolStripSeparator2.Size = new System.Drawing.Size(6, 25);
+			// 
+			// autoscroll
+			// 
+			this.autoscroll.Checked = true;
+			this.autoscroll.Margin = new System.Windows.Forms.Padding(3, 3, 0, 2);
+			this.autoscroll.Name = "autoscroll";
+			this.autoscroll.Size = new System.Drawing.Size(84, 20);
+			this.autoscroll.Text = "Auto Scroll";
+			// 
+			// console
+			// 
+			this.console.BackColor = System.Drawing.SystemColors.Window;
+			this.console.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+			this.console.Dock = System.Windows.Forms.DockStyle.Fill;
+			this.console.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
+			this.console.Location = new System.Drawing.Point(0, 25);
+			this.console.Name = "console";
+			this.console.ReadOnly = true;
+			this.console.Size = new System.Drawing.Size(800, 125);
+			this.console.TabIndex = 1;
+			this.console.Text = "";
+			// 
+			// clearall
+			// 
+			this.clearall.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+			this.clearall.Image = global::CodeImp.DoomBuilder.Properties.Resources.Clear;
+			this.clearall.ImageTransparentColor = System.Drawing.Color.Magenta;
+			this.clearall.Name = "clearall";
+			this.clearall.Size = new System.Drawing.Size(23, 22);
+			this.clearall.Text = "Clear All";
+			this.clearall.Click += new System.EventHandler(this.clearall_Click);
+			// 
+			// wordwrap
+			// 
+			this.wordwrap.CheckOnClick = true;
+			this.wordwrap.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+			this.wordwrap.Image = global::CodeImp.DoomBuilder.Properties.Resources.WordWrap;
+			this.wordwrap.ImageTransparentColor = System.Drawing.Color.Magenta;
+			this.wordwrap.Name = "wordwrap";
+			this.wordwrap.Size = new System.Drawing.Size(23, 22);
+			this.wordwrap.Text = "Word Wrap";
+			this.wordwrap.Click += new System.EventHandler(this.wordwrap_Click);
+			// 
+			// filterselector
+			// 
+			this.filterselector.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+			this.filterselector.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.filterlog,
+            this.filterinfo,
+            this.filterwarning,
+            this.filtererrors,
+            this.filterspecial});
+			this.filterselector.Image = global::CodeImp.DoomBuilder.Properties.Resources.Filter;
+			this.filterselector.ImageTransparentColor = System.Drawing.Color.Magenta;
+			this.filterselector.Name = "filterselector";
+			this.filterselector.Size = new System.Drawing.Size(29, 22);
+			this.filterselector.Text = "Displayed Message Types";
+			// 
+			// filterlog
+			// 
+			this.filterlog.CheckOnClick = true;
+			this.filterlog.Name = "filterlog";
+			this.filterlog.Size = new System.Drawing.Size(124, 22);
+			this.filterlog.Tag = 1;
+			this.filterlog.Text = "Log";
+			this.filterlog.Click += new System.EventHandler(this.filters_Click);
+			// 
+			// filterinfo
+			// 
+			this.filterinfo.Checked = true;
+			this.filterinfo.CheckOnClick = true;
+			this.filterinfo.CheckState = System.Windows.Forms.CheckState.Checked;
+			this.filterinfo.Name = "filterinfo";
+			this.filterinfo.Size = new System.Drawing.Size(124, 22);
+			this.filterinfo.Tag = 2;
+			this.filterinfo.Text = "Info";
+			this.filterinfo.Click += new System.EventHandler(this.filters_Click);
+			// 
+			// filterwarning
+			// 
+			this.filterwarning.Checked = true;
+			this.filterwarning.CheckOnClick = true;
+			this.filterwarning.CheckState = System.Windows.Forms.CheckState.Checked;
+			this.filterwarning.Name = "filterwarning";
+			this.filterwarning.Size = new System.Drawing.Size(124, 22);
+			this.filterwarning.Tag = 4;
+			this.filterwarning.Text = "Warnings";
+			this.filterwarning.Click += new System.EventHandler(this.filters_Click);
+			// 
+			// filtererrors
+			// 
+			this.filtererrors.Checked = true;
+			this.filtererrors.CheckOnClick = true;
+			this.filtererrors.CheckState = System.Windows.Forms.CheckState.Checked;
+			this.filtererrors.Name = "filtererrors";
+			this.filtererrors.Size = new System.Drawing.Size(124, 22);
+			this.filtererrors.Tag = 8;
+			this.filtererrors.Text = "Errors";
+			this.filtererrors.Click += new System.EventHandler(this.filters_Click);
+			// 
+			// filterspecial
+			// 
+			this.filterspecial.Checked = true;
+			this.filterspecial.CheckOnClick = true;
+			this.filterspecial.CheckState = System.Windows.Forms.CheckState.Checked;
+			this.filterspecial.Name = "filterspecial";
+			this.filterspecial.Size = new System.Drawing.Size(124, 22);
+			this.filterspecial.Tag = 16;
+			this.filterspecial.Text = "Special";
+			this.filterspecial.Click += new System.EventHandler(this.filters_Click);
+			// 
+			// searchclear
+			// 
+			this.searchclear.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+			this.searchclear.Image = global::CodeImp.DoomBuilder.Properties.Resources.SearchClear;
+			this.searchclear.ImageTransparentColor = System.Drawing.Color.Magenta;
+			this.searchclear.Name = "searchclear";
+			this.searchclear.Size = new System.Drawing.Size(23, 22);
+			this.searchclear.Text = "Clear Filter";
+			this.searchclear.Click += new System.EventHandler(this.searchclear_Click);
+			// 
+			// alwaysontop
+			// 
+			this.alwaysontop.CheckOnClick = true;
+			this.alwaysontop.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+			this.alwaysontop.Image = global::CodeImp.DoomBuilder.Properties.Resources.Pin;
+			this.alwaysontop.ImageTransparentColor = System.Drawing.Color.Magenta;
+			this.alwaysontop.Name = "alwaysontop";
+			this.alwaysontop.Size = new System.Drawing.Size(23, 22);
+			this.alwaysontop.Text = "Stay on Top";
+			// 
+			// DebugConsole
+			// 
+			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
+			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+			this.Controls.Add(this.console);
+			this.Controls.Add(this.toolStrip1);
+			this.Name = "DebugConsole";
+			this.Size = new System.Drawing.Size(800, 150);
+			this.toolStrip1.ResumeLayout(false);
+			this.toolStrip1.PerformLayout();
+			this.ResumeLayout(false);
+			this.PerformLayout();
+
+		}
+
+		#endregion
+
+		private System.Windows.Forms.ToolStrip toolStrip1;
+		private System.Windows.Forms.ToolStripButton clearall;
+		private System.Windows.Forms.ToolStripDropDownButton filterselector;
+		private System.Windows.Forms.ToolStripMenuItem filterlog;
+		private System.Windows.Forms.ToolStripMenuItem filterwarning;
+		private System.Windows.Forms.ToolStripMenuItem filtererrors;
+		private System.Windows.Forms.ToolStripMenuItem filterspecial;
+		private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
+		private System.Windows.Forms.ToolStripLabel toolStripLabel1;
+		private System.Windows.Forms.ToolStripTextBox searchbox;
+		private System.Windows.Forms.ToolStripButton searchclear;
+		private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
+		private System.Windows.Forms.ToolStripMenuItem filterinfo;
+		private System.Windows.Forms.ToolStripButton wordwrap;
+		private System.Windows.Forms.ToolStripSeparator toolStripSeparator3;
+		private System.Windows.Forms.RichTextBox console;
+		private System.Windows.Forms.ToolStripButton alwaysontop;
+		private CodeImp.DoomBuilder.Controls.ToolStripCheckBox autoscroll;
+	}
+}
diff --git a/Source/Core/Controls/DebugConsole.resx b/Source/Core/Controls/DebugConsole.resx
new file mode 100644
index 000000000..36f2967b5
--- /dev/null
+++ b/Source/Core/Controls/DebugConsole.resx
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>
\ No newline at end of file
diff --git a/Source/Core/General/General.cs b/Source/Core/General/General.cs
index 69d56732b..a1b7d4538 100644
--- a/Source/Core/General/General.cs
+++ b/Source/Core/General/General.cs
@@ -1621,8 +1621,9 @@ namespace CodeImp.DoomBuilder
 		public static void WriteLogLine(string line)
 		{
 #if DEBUG
-			// Output to console
+			// Output to consoles
 			Console.WriteLine(line);
+			DebugConsole.WriteLine(DebugMessageType.Log, line); //mxd
 #endif
 			// Write to log file
 			try { File.AppendAllText(logfile, line + Environment.NewLine); }
@@ -1633,8 +1634,9 @@ namespace CodeImp.DoomBuilder
 		public static void WriteLog(string text)
 		{
 #if DEBUG
-			// Output to console
+			// Output to consoles
 			Console.Write(text);
+			DebugConsole.Write(DebugMessageType.Log, text);
 #endif
 
 			// Write to log file
diff --git a/Source/Core/General/MurmurHash2.cs b/Source/Core/General/MurmurHash2.cs
new file mode 100644
index 000000000..da5e7e48d
--- /dev/null
+++ b/Source/Core/General/MurmurHash2.cs
@@ -0,0 +1,109 @@
+#region ============== License
+
+/***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HashTableHashing.MurmurHash2.
+ *
+ * The Initial Developer of the Original Code is
+ * Davy Landman.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#endregion
+
+using System;
+using System.Text;
+
+namespace CodeImp.DoomBuilder
+{
+	internal static class MurmurHash2
+	{
+		private const UInt32 m = 0x5bd1e995;
+		private const Int32 r = 24;
+
+		public static UInt32 Hash(string data) 
+		{
+			return Hash(Encoding.ASCII.GetBytes(data), 0xc58f1a7b);
+		}
+
+		private static unsafe UInt32 Hash(Byte[] data, UInt32 seed) 
+		{
+			Int32 length = data.Length;
+			if(length == 0) return 0;
+
+			UInt32 h = seed ^ (UInt32)length;
+			Int32 remainingBytes = length & 3; // mod 4
+			Int32 numberOfLoops = length >> 2; // div 4
+			
+			fixed(byte* firstByte = &(data[0])) 
+			{
+				UInt32* realData = (UInt32*)firstByte;
+				while(numberOfLoops != 0) 
+				{
+					UInt32 k = *realData;
+					k *= m;
+					k ^= k >> r;
+					k *= m;
+
+					h *= m;
+					h ^= k;
+					numberOfLoops--;
+					realData++;
+				}
+
+				switch(remainingBytes) 
+				{
+					case 3:
+						h ^= (UInt16)(*realData);
+						h ^= ((UInt32)(*(((Byte*)(realData)) + 2))) << 16;
+						h *= m;
+						break;
+					case 2:
+						h ^= (UInt16)(*realData);
+						h *= m;
+						break;
+					case 1:
+						h ^= *((Byte*)realData);
+						h *= m;
+						break;
+				}
+			}
+
+			// Do a few final mixes of the hash to ensure the last few
+			// bytes are well-incorporated.
+			h ^= h >> 13;
+			h *= m;
+			h ^= h >> 15;
+
+			return h;
+		}
+	}
+}
diff --git a/Source/Core/Properties/Resources.Designer.cs b/Source/Core/Properties/Resources.Designer.cs
index bd29b50cd..c53901286 100644
--- a/Source/Core/Properties/Resources.Designer.cs
+++ b/Source/Core/Properties/Resources.Designer.cs
@@ -102,6 +102,13 @@ namespace CodeImp.DoomBuilder.Properties {
             }
         }
         
+        internal static System.Drawing.Bitmap Clear {
+            get {
+                object obj = ResourceManager.GetObject("Clear", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
         internal static System.Drawing.Bitmap ClearTextures {
             get {
                 object obj = ResourceManager.GetObject("ClearTextures", resourceCulture);
@@ -816,6 +823,13 @@ namespace CodeImp.DoomBuilder.Properties {
             }
         }
         
+        internal static System.Drawing.Bitmap WordWrap {
+            get {
+                object obj = ResourceManager.GetObject("WordWrap", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
         internal static System.Drawing.Bitmap Zoom {
             get {
                 object obj = ResourceManager.GetObject("Zoom", resourceCulture);
diff --git a/Source/Core/Properties/Resources.resx b/Source/Core/Properties/Resources.resx
index fef785c34..83c6b255b 100644
--- a/Source/Core/Properties/Resources.resx
+++ b/Source/Core/Properties/Resources.resx
@@ -121,15 +121,15 @@
   <data name="Zoom" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Zoom.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
+  <data name="Clear" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Clear.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
   <data name="Cut" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Cut.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
   <data name="ArrowUp" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\ArrowUp.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
-  <data name="ClearTextures" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\ClearTextures.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
-  </data>
   <data name="Keyboard" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Keyboard.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
@@ -172,8 +172,8 @@
   <data name="Grid4" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Grid4.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
-  <data name="Status10" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\Status10.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  <data name="MixedThings" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\MixedThings.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
   <data name="Marine" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Marine.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
@@ -202,8 +202,8 @@
   <data name="ThingStatistics" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\ThingStatistics.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
-  <data name="Grid2_arrowup" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\Grid2_arrowup.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  <data name="Redo" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Redo.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
   <data name="Grid2" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Grid2.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
@@ -274,6 +274,9 @@
   <data name="treeview" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\treeview.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
+  <data name="Status1" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Status1.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
   <data name="PasteSpecial" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\PasteSpecial.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
@@ -298,6 +301,9 @@
   <data name="InfoLine" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\InfoLine.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
+  <data name="Status10" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Status10.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
   <data name="MissingTexture" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\MissingTexture.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
@@ -343,8 +349,11 @@
   <data name="Script2" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Script2.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
-  <data name="Redo" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\Redo.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  <data name="ClearTextures" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\ClearTextures.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="Status2" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Status2.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
   <data name="Prefab" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Prefab.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
@@ -364,8 +373,8 @@
   <data name="Status12" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Status12.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
-  <data name="Status2" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\Status2.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  <data name="Light_animate" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Light_animate.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
   <data name="Link" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Link.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
@@ -391,8 +400,8 @@
   <data name="ViewTextureFloor" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\ViewTextureFloor.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
-  <data name="Light_animate" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\Light_animate.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  <data name="Grid2_arrowup" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Grid2_arrowup.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
   <data name="Pin" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Pin.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
@@ -427,8 +436,8 @@
   <data name="Status11" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Status11.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
-  <data name="Status1" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\Status1.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  <data name="ScreenshotActiveWindow" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\ScreenshotActiveWindow.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
   <data name="WarningLarge" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\WarningLarge.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
@@ -442,10 +451,7 @@
   <data name="Check" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Check.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
-  <data name="ScreenshotActiveWindow" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\ScreenshotActiveWindow.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
-  </data>
-  <data name="MixedThings" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\MixedThings.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  <data name="WordWrap" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\WordWrap.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
 </root>
\ No newline at end of file
diff --git a/Source/Core/Resources/Clear.png b/Source/Core/Resources/Clear.png
new file mode 100644
index 0000000000000000000000000000000000000000..71074738471ef7c71d98029bd378d4f3ccd02c88
GIT binary patch
literal 706
zcmV;z0zLhSP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!WJyFpRCwBKlTBz7K@`W|ZWBWSCh_1c
zBoN3&kU|w!DT0Q?kcL3C=s_un9s&hvAcs&T#ZV}@<RF4&L6FuDJSlC8y;O4$(HvUv
zCLZ!}6p~FsNp|zGJHDA_!@AADpPAj6-+TWz9~_vP-|vS=Bmxof0C@c7P&{w~0SEyQ
z4*P=<z)uwjj8GOG^!a=c3<e<(2!PdUHO9Q60SPU9e!I?b92BGX^|(Liq{ZX$!2J9?
z%WSjR%!aON0O441E7xi^(Fzao{dc)f(R(w@7{GTvkw^dyGNn=Y=EBbFoP;nTXkODm
zk|emb|9(vch~b)R{4^35&;H(cbTRtz)ibHlU@$>g4^lZgg7D_XDh7%3)8Kx;FQif_
zaTLDHKp4Yx+^kkl#)NI+3~z=NMG?~Jv^W?HKvh+6+zCyBG{&&Y<$~Si)#vzkk2k`y
zEDM=TMnne;)@8eW1O8Sj7(-)l@$u6ac#ON~qr@9CXR}!m`~P67DrVLegACqFt-ZwB
zcM$u^j#1B?&*#NnuXk!g>-9RG9uH2Z6Ba*gCLeO7Y9;+I4${WVGFWKlLZKjbyWLZ8
ztyY`Bu~;nWJw7g<)udlmqOf_DI?zFC)!3+Q9ey)6_w9+Itenv_SqE9^0Ed=#yIqdQ
z<H=&ND0Vs>Hc`P1qt}+!jJGtoF3T%-mzFM=9d2!J1Lt<TX_i^cCX`AgvDIo#76^in
zL}%p(nzrl(xMU8159yFTigT#*cDvn=S-r#Ikd-sOEPtpW)DSfo_f#WV$#uLPG=LrD
oK-o^pV$uv!4ZUibBmV>#0B9IgxX6vuQUCw|07*qoM6N<$f+>GJAOHXW

literal 0
HcmV?d00001

diff --git a/Source/Core/Resources/WordWrap.png b/Source/Core/Resources/WordWrap.png
new file mode 100644
index 0000000000000000000000000000000000000000..7f630347d910b890fb3a4c5475fc3903e5eb1a6f
GIT binary patch
literal 1148
zcmdT?J!q3r6g{5=znWSISW43t+}s2Sgn+mhQ%o#Y(JE0Kba0h|aqAEZ!IrACh`0(4
zX<WJnBwInTxCEp~DJt=&LyY<B_n!CMmmhF->V^B=`|dmM+;iXiRxi(=9}7=~0gPqR
zsVfpA^pV5zJzlqWBpg{x-?$Hq9;17B{$f&!elBxy*8g~PDjW`7I_;kTLerVl*@eld
z7YXfk1@V98@%Y;7n(cHA+T8#h@ph-5TeSDxTI+&gRBXq^LnN?tYsCR$JzWFJt+jrI
zSQ_y(#t@U}$Tn_WOTZY5z@X<w)<W2?1*{u!PnsBPRd?|4(W)aLiQsVW*$>p}z^}SN
z{dW_s#vZzjpV)7HN9E-LqT@cH`24MffO5rD0TU8Dy*n-)oC?=@W~e~a?AF?tnebJa
z<pD9h115SvSQ&XA+~){e?<#n*vgGz<tytavhk%SS8i=91_nU8V^ZFc;$s}U2m=cm$
z2?929Y{L<RfEP%T$18ah3Izr8lg(xaITOp5>X8jc&?377ix2WBl}d=m<9e`IEMogh
z6Q6clsP6m`HEiY-3<xNjH7gQ0PU6f3tZ!75s9Y}V9`A=37u`665vWLRLB|lBcHuau
z5^0=_r`;jeMHBaX6AYj6L-ni5($|nSfm{fG7$Y+U8u=4Z0X@*j`34GdOL?vjpq<NI
TeHE!T-cWjGc0N^1-d_F-Q{QR?

literal 0
HcmV?d00001

diff --git a/Source/Core/VisualModes/VisualMode.cs b/Source/Core/VisualModes/VisualMode.cs
index d098a8c77..509a8b6ea 100644
--- a/Source/Core/VisualModes/VisualMode.cs
+++ b/Source/Core/VisualModes/VisualMode.cs
@@ -416,38 +416,46 @@ namespace CodeImp.DoomBuilder.VisualModes
 
 		//mxd
 		[BeginAction("movethingleft", BaseAction = true)]
-		protected void moveSelectedThingsLeft() {
-			moveSelectedThings(new Vector2D(0f, -General.Map.Grid.GridSize), false);
+		protected void MoveSelectedThingsLeft() 
+		{
+			MoveSelectedThings(new Vector2D(0f, -General.Map.Grid.GridSize), false);
 		}
 		//mxd
 		[BeginAction("movethingright", BaseAction = true)]
-		protected void moveSelectedThingsRight() {
-			moveSelectedThings(new Vector2D(0f, General.Map.Grid.GridSize), false);
+		protected void MoveSelectedThingsRight() 
+		{
+			MoveSelectedThings(new Vector2D(0f, General.Map.Grid.GridSize), false);
 		}
 		//mxd
 		[BeginAction("movethingfwd", BaseAction = true)]
-		protected void moveSelectedThingsForward() {
-			moveSelectedThings(new Vector2D(-General.Map.Grid.GridSize, 0f), false);
+		protected void MoveSelectedThingsForward() 
+		{
+			MoveSelectedThings(new Vector2D(-General.Map.Grid.GridSize, 0f), false);
 		}
 		//mxd
 		[BeginAction("movethingback", BaseAction = true)]
-		protected void moveSelectedThingsBackward() {
-			moveSelectedThings(new Vector2D(General.Map.Grid.GridSize, 0f), false);
+		protected void MoveSelectedThingsBackward() 
+		{
+			MoveSelectedThings(new Vector2D(General.Map.Grid.GridSize, 0f), false);
 		}
+
 		//mxd
 		[BeginAction("placethingatcursor", BaseAction = true)]
-		protected void placeThingAtCursor() {
+		protected void PlaceThingAtCursor() 
+		{
 			Vector2D hitpos = GetHitPosition();
-			if (!hitpos.IsFinite()) {
+			if (!hitpos.IsFinite()) 
+			{
 				General.Interface.DisplayStatus(StatusType.Warning, "Cannot place Thing here");
 				return;
 			}
 
-			moveSelectedThings(new Vector2D((float)Math.Round(hitpos.x), (float)Math.Round(hitpos.y)), true);
+			MoveSelectedThings(new Vector2D((float)Math.Round(hitpos.x), (float)Math.Round(hitpos.y)), true);
 		}
 
 		//mxd. 
-		public Vector2D GetHitPosition() {
+		public Vector2D GetHitPosition() 
+		{
 			Vector3D start = General.Map.VisualCamera.Position;
 			Vector3D delta = General.Map.VisualCamera.Target - General.Map.VisualCamera.Position;
 			delta = delta.GetFixedLength(General.Settings.ViewDistance * 0.98f);
@@ -456,12 +464,14 @@ namespace CodeImp.DoomBuilder.VisualModes
 			if (target.picked == null) return new Vector2D(float.NaN, float.NaN);
 
 			//now find where exactly did we hit
-			if (target.picked is VisualGeometry) {
+			if (target.picked is VisualGeometry) 
+			{
 				VisualGeometry vg = target.picked as VisualGeometry;
 				return getIntersection(start, start + delta, new Vector3D(vg.BoundingBox[0].X, vg.BoundingBox[0].Y, vg.BoundingBox[0].Z), new Vector3D(vg.Vertices[0].nx, vg.Vertices[0].ny, vg.Vertices[0].nz));
 			} 
 			
-			if (target.picked is VisualThing) {
+			if (target.picked is VisualThing) 
+			{
 				VisualThing vt = target.picked as VisualThing;
 				return getIntersection(start, start + delta, new Vector3D(vt.BoundingBox[0].X, vt.BoundingBox[0].Y, vt.BoundingBox[0].Z), D3DDevice.V3D(vt.Center - vt.PositionV3));
 			} 
@@ -477,7 +487,7 @@ namespace CodeImp.DoomBuilder.VisualModes
 		}
 
 		//mxd. Should move selected things in specified direction
-		protected abstract void moveSelectedThings(Vector2D direction, bool absolutePosition);
+		protected virtual void MoveSelectedThings(Vector2D direction, bool absolutePosition) { }
 		
 		#endregion
 
diff --git a/Source/Core/Windows/MainForm.Designer.cs b/Source/Core/Windows/MainForm.Designer.cs
index 5de47533f..91bd40e78 100644
--- a/Source/Core/Windows/MainForm.Designer.cs
+++ b/Source/Core/Windows/MainForm.Designer.cs
@@ -228,6 +228,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.linedefinfo = new CodeImp.DoomBuilder.Controls.LinedefInfoPanel();
 			this.thinginfo = new CodeImp.DoomBuilder.Controls.ThingInfoPanel();
 			this.sectorinfo = new CodeImp.DoomBuilder.Controls.SectorInfoPanel();
+			this.console = new CodeImp.DoomBuilder.DebugConsole();
 			this.redrawtimer = new System.Windows.Forms.Timer(this.components);
 			this.display = new RenderTargetControl();
 			this.processor = new System.Windows.Forms.Timer(this.components);
@@ -363,7 +364,7 @@ namespace CodeImp.DoomBuilder.Windows
             this.menuhelp});
 			this.menumain.Location = new System.Drawing.Point(0, 0);
 			this.menumain.Name = "menumain";
-			this.menumain.Size = new System.Drawing.Size(420, 24);
+			this.menumain.Size = new System.Drawing.Size(328, 24);
 			this.menumain.TabIndex = 0;
 			// 
 			// menufile
@@ -2009,6 +2010,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.panelinfo.Controls.Add(this.labelcollapsedinfo);
 			this.panelinfo.Controls.Add(this.modename);
 			this.panelinfo.Controls.Add(this.buttontoggleinfo);
+			this.panelinfo.Controls.Add(this.console);
 			this.panelinfo.Controls.Add(this.vertexinfo);
 			this.panelinfo.Controls.Add(this.linedefinfo);
 			this.panelinfo.Controls.Add(this.thinginfo);
@@ -2125,6 +2127,16 @@ namespace CodeImp.DoomBuilder.Windows
 			this.sectorinfo.TabIndex = 2;
 			this.sectorinfo.Visible = false;
 			// 
+			// console
+			// 
+			this.console.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
+						| System.Windows.Forms.AnchorStyles.Left)
+						| System.Windows.Forms.AnchorStyles.Right)));
+			this.console.Location = new System.Drawing.Point(3, 3);
+			this.console.Name = "console";
+			this.console.Size = new System.Drawing.Size(851, 98);
+			this.console.TabIndex = 10;
+			// 
 			// redrawtimer
 			// 
 			this.redrawtimer.Interval = 1;
@@ -2239,7 +2251,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.KeyPreview = true;
 			this.MainMenuStrip = this.menumain;
 			this.Name = "MainForm";
-			this.Opacity = 0;
+			this.Opacity = 1;
 			this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
 			this.Text = "GZDoom Builder";
 			this.Deactivate += new System.EventHandler(this.MainForm_Deactivate);
@@ -2470,5 +2482,6 @@ namespace CodeImp.DoomBuilder.Windows
 		private ToolStripMenuItem modelsdontshow;
 		private ToolStripMenuItem modelsshowselection;
 		private ToolStripMenuItem modelsshowall;
+		private DebugConsole console;
 	}
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/MainForm.cs b/Source/Core/Windows/MainForm.cs
index 977fbe855..7d2a3f535 100644
--- a/Source/Core/Windows/MainForm.cs
+++ b/Source/Core/Windows/MainForm.cs
@@ -256,6 +256,13 @@ namespace CodeImp.DoomBuilder.Windows
 			blinkTimer = new System.Timers.Timer {Interval = 500};
 			blinkTimer.Elapsed += blinkTimer_Elapsed;
 
+			//mxd. Debug Console
+#if DEBUG
+			modename.Visible = false;
+#else
+			console.Visible = false;
+#endif
+
 			//mxd. Hints
 			hintsPanel = new HintsPanel();
 			hintsDocker = new Docker("hints", "Help", hintsPanel);
@@ -3092,6 +3099,9 @@ namespace CodeImp.DoomBuilder.Windows
 				if(sectorinfo.Visible) sectorinfo.Hide();
 				if(thinginfo.Visible) thinginfo.Hide();
 				modename.Visible = false;
+#if DEBUG
+				console.Visible = false; //mxd
+#endif
 				statistics.Visible = false; //mxd
 				labelcollapsedinfo.Visible = true;
 				itemtoggleinfo.Checked = false;
@@ -3143,7 +3153,11 @@ namespace CodeImp.DoomBuilder.Windows
 			if(thinginfo.Visible) thinginfo.Hide();
 			labelcollapsedinfo.Text = modename.Text;
 			labelcollapsedinfo.Refresh();
+#if DEBUG
+			console.Visible = true;
+#else
 			modename.Visible = showModeName;
+#endif
 			modename.Refresh();
 			statistics.Visible = showModeName; //mxd
 
@@ -3211,6 +3225,9 @@ namespace CodeImp.DoomBuilder.Windows
 			
 			lastinfoobject = l;
 			modename.Visible = false;
+#if DEBUG
+			console.Visible = console.AlwaysOnTop; //mxd
+#endif
 			statistics.Visible = false; //mxd
 			if(vertexinfo.Visible) vertexinfo.Hide();
 			if(sectorinfo.Visible) sectorinfo.Hide();
@@ -3241,6 +3258,9 @@ namespace CodeImp.DoomBuilder.Windows
 
 			lastinfoobject = v;
 			modename.Visible = false;
+#if DEBUG
+			console.Visible = console.AlwaysOnTop; //mxd
+#endif
 			statistics.Visible = false; //mxd
 			if (linedefinfo.Visible) linedefinfo.Hide();
 			if (sectorinfo.Visible) sectorinfo.Hide();
@@ -3264,6 +3284,9 @@ namespace CodeImp.DoomBuilder.Windows
 
 			lastinfoobject = s;
 			modename.Visible = false;
+#if DEBUG
+			console.Visible = console.AlwaysOnTop; //mxd
+#endif
 			statistics.Visible = false; //mxd
 			if (linedefinfo.Visible) linedefinfo.Hide();
 			if (vertexinfo.Visible) vertexinfo.Hide();
@@ -3295,6 +3318,9 @@ namespace CodeImp.DoomBuilder.Windows
 
 			lastinfoobject = t;
 			modename.Visible = false;
+#if DEBUG
+			console.Visible = console.AlwaysOnTop; //mxd
+#endif
 			statistics.Visible = false; //mxd
 			if(linedefinfo.Visible) linedefinfo.Hide();
 			if(vertexinfo.Visible) vertexinfo.Hide();
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
index 5dcd4cb24..e5fe266ea 100644
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
@@ -519,7 +519,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		}
 
 		//mxd
-		protected override void moveSelectedThings(Vector2D direction, bool absolutePosition) {
+		protected override void MoveSelectedThings(Vector2D direction, bool absolutePosition) 
+		{
 			List<VisualThing> visualThings = GetSelectedVisualThings(true);
 			if (visualThings.Count == 0) return;
 
@@ -531,7 +532,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 			//move things...
 			Vector3D[] translatedCoords = translateCoordinates(coords, direction, absolutePosition);
-			for (int i = 0; i < visualThings.Count; i++) {
+			for (int i = 0; i < visualThings.Count; i++) 
+			{
 				BaseVisualThing t = visualThings[i] as BaseVisualThing;
 				t.OnMove(translatedCoords[i]);
 			}
-- 
GitLab