ZoneBuilder/Source/Plugins/BuilderModes/Interface/ErrorCheckForm.cs
MaxED 154ceb0eb7 Added "safeboundary" game configuration property. The value determines the maximum map bounding box size considered to be safe by "Check map size" error check.
Changed, Map Analysis mode: some checks are now available only under certain conditions (for example, "Check polyobjects" is now available only when the map is in Hexen or UDMF map format).
Updated documentation ("Game Configuration - Basic Settings" page).
2023-01-04 22:59:30 +01:00

827 lines
No EOL
21 KiB
C#

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Editing;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Windows;
using System.Reflection;
using System.Globalization;
using System.Threading;
#endregion
namespace CodeImp.DoomBuilder.BuilderModes
{
public partial class ErrorCheckForm : DelayedForm
{
#region ================== Constants
#endregion
#region ================== Delegates
private delegate void CallVoidMethodDeletage();
private delegate void CallIntMethodDelegate(int i);
private delegate void CallResultMethodDelegate(ErrorResult r);
#endregion
#region ================== Variables
private volatile bool running;
private Thread checksthread;
private BlockMap<BlockEntry> blockmap;
private Size initialsize; //mxd
private List<ErrorResult> resultslist; //mxd
private List<Type> hiddentresulttypes; //mxd
private bool bathselectioninprogress; //mxd
#endregion
#region ================== Properties
public List<ErrorResult> SelectedResults {
get
{
List<ErrorResult> selection = new List<ErrorResult>();
foreach(Object ro in results.SelectedItems)
{
ErrorResult result = ro as ErrorResult;
if(result == null) continue;
selection.Add(result);
}
return selection;
}
}
public BlockMap<BlockEntry> BlockMap { get { return blockmap; } }
#endregion
#region ================== Constructor / Show
// Constructor
public ErrorCheckForm()
{
// Initialize
InitializeComponent();
// Find all error checkers
Type[] checkertypes = BuilderPlug.Me.FindClasses(typeof(ErrorChecker));
foreach(Type t in checkertypes)
{
object[] attr = t.GetCustomAttributes(typeof(ErrorCheckerAttribute), true);
if(attr.Length > 0)
{
//mxd. Skip this check?..
ErrorChecker checker;
try
{
// Create instance
checker = (ErrorChecker)Assembly.GetExecutingAssembly().CreateInstance(t.FullName, false, BindingFlags.Default, null, null, CultureInfo.CurrentCulture, new object[0]);
}
catch(TargetInvocationException ex)
{
// Error!
General.ErrorLogger.Add(ErrorType.Error, "Failed to create class instance \"" + t.Name + "\"");
General.WriteLogLine(ex.InnerException.GetType().Name + ": " + ex.InnerException.Message);
throw;
}
catch(Exception ex)
{
// Error!
General.ErrorLogger.Add(ErrorType.Error, "Failed to create class instance \"" + t.Name + "\"");
General.WriteLogLine(ex.GetType().Name + ": " + ex.Message);
throw;
}
if(checker.SkipCheck) continue;
ErrorCheckerAttribute checkerattr = (attr[0] as ErrorCheckerAttribute);
// Add the type to the checkbox list
CheckBox c = checks.Add(checkerattr.DisplayName, t);
c.Checked = checkerattr.DefaultChecked;
}
}
checks.Sort(); //mxd
//mxd. Store initial height
initialsize = this.Size;
resultslist = new List<ErrorResult>();
hiddentresulttypes = new List<Type>();
}
// This shows the window
public void Show(Form owner)
{
// Move controls according to the height of the checkers box
checks.PerformLayout();
buttoncheck.Top = checks.Bottom + 14;
resultspanel.Top = buttoncheck.Bottom + 14;
this.Text = "Map Analysis"; //mxd
// Position at left-top of owner
this.Location = new Point(owner.Location.X + 20, owner.Location.Y + 90);
// Close results part
resultspanel.Visible = false;
this.Size = new Size(initialsize.Width, this.Height - this.ClientSize.Height + resultspanel.Top);
this.MinimumSize = this.Size; //mxd
this.MaximumSize = this.Size; //mxd
// Show window
base.Show(owner);
}
#endregion
#region ================== Thread Calls
public void SubmitResult(ErrorResult result)
{
if(results.InvokeRequired)
{
CallResultMethodDelegate d = SubmitResult;
try { progress.Invoke(d, result); }
catch(ThreadInterruptedException) { }
}
else
{
if(!result.IsHidden && !hiddentresulttypes.Contains(result.GetType())) //mxd
{
results.Items.Add(result);
}
resultslist.Add(result); //mxd
UpdateTitle();
}
}
private void SetProgressMaximum(int maximum)
{
if(progress.InvokeRequired)
{
CallIntMethodDelegate d = SetProgressMaximum;
try { progress.Invoke(d, maximum); }
catch(ThreadInterruptedException) { }
}
else
{
progress.Maximum = maximum;
}
}
public void AddProgressValue(int value)
{
if(progress.InvokeRequired)
{
CallIntMethodDelegate d = AddProgressValue;
try { progress.Invoke(d, value); }
catch(ThreadInterruptedException) { }
}
else
{
progress.Value += value;
}
}
//mxd
private void UpdateTitle()
{
int hiddencount = resultslist.Count - results.Items.Count;
string title = "Map Analysis [" + resultslist.Count + " results";
if(hiddencount > 0) title += hiddencount + " hidden";
title += ", " + results.SelectedItems.Count + " selected";
this.Text = title + @"]";
}
// This stops checking (only called from the checking management thread)
private void StopChecking()
{
if(this.InvokeRequired)
{
CallVoidMethodDeletage d = StopChecking;
this.Invoke(d);
}
else
{
checksthread = null;
progress.Value = 0;
buttoncheck.Text = "Start Analysis";
Cursor.Current = Cursors.Default;
running = false;
blockmap.Dispose();
blockmap = null;
// When no results found, show "no results" and disable the list
if(resultslist.Count == 0)
{
results.Items.Add(new ResultNoErrors());
results.Enabled = false;
UpdateTitle(); //mxd
}
else
{
ClearSelectedResult(); //mxd
}
}
}
// This starts checking
private void StartChecking()
{
if(running) return;
Cursor.Current = Cursors.WaitCursor;
// Make blockmap
RectangleF area = MapSet.CreateArea(General.Map.Map.Vertices);
area = MapSet.IncreaseArea(area, General.Map.Map.Things);
blockmap = new BlockMap<BlockEntry>(area);
blockmap.AddLinedefsSet(General.Map.Map.Linedefs);
blockmap.AddSectorsSet(General.Map.Map.Sectors);
blockmap.AddThingsSet(General.Map.Map.Things);
blockmap.AddVerticesSet(General.Map.Map.Vertices); //mxd
//mxd. Open the results panel
if(!resultspanel.Visible)
{
this.MinimumSize = new Size();
this.MaximumSize = new Size();
this.Size = initialsize;
resultspanel.Size = new Size(resultspanel.Width, this.ClientSize.Height - resultspanel.Top);
resultspanel.Visible = true;
}
progress.Value = 0;
results.Items.Clear();
results.Enabled = true;
resultslist = new List<ErrorResult>(); //mxd
ClearSelectedResult();
buttoncheck.Text = "Abort Analysis";
General.Interface.RedrawDisplay();
// Start checking
running = true;
checksthread = new Thread(RunChecks);
checksthread.Name = "Error Checking Management";
checksthread.Priority = ThreadPriority.Normal;
checksthread.Start();
Cursor.Current = Cursors.Default;
}
#endregion
#region ================== Methods
// This stops the checking
public void CloseWindow()
{
// Currently running?
if(running)
{
Cursor.Current = Cursors.WaitCursor;
checksthread.Interrupt();
}
ClearSelectedResult();
//mxd. Clear results
resultslist.Clear();
results.Items.Clear();
this.Hide();
}
// This clears the selected result
private void ClearSelectedResult()
{
results.SelectedItems.Clear(); //mxd
if(results.Items.Count == 0 && resultslist.Count > 0) //mxd
resultinfo.Text = "All results are hidden. Use context menu to show them.";
else
resultinfo.Text = "Select a result from the list to see more information.\r\nHold 'Ctrl' to select several results.\r\nHold 'Shift' to select a range of results.\r\nRight-click on a result to show context menu.";
resultinfo.Enabled = false;
fix1.Visible = false;
fix2.Visible = false;
fix3.Visible = false;
UpdateTitle(); //mxd
}
// This runs in a seperate thread to manage the checking threads
private void RunChecks()
{
List<ErrorChecker> checkers = new List<ErrorChecker>();
List<Thread> threads = new List<Thread>();
int maxthreads = Environment.ProcessorCount;
int totalprogress = 0;
int nextchecker = 0;
// Initiate all checkers
foreach(CheckBox c in checks.Checkboxes)
{
// Include this one?
if(c.Checked)
{
Type t = (c.Tag as Type);
ErrorChecker checker;
try
{
// Create instance
checker = (ErrorChecker)Assembly.GetExecutingAssembly().CreateInstance(t.FullName, false, BindingFlags.Default, null, null, CultureInfo.CurrentCulture, new object[0]);
}
catch(TargetInvocationException ex)
{
// Error!
General.ErrorLogger.Add(ErrorType.Error, "Failed to create class instance \"" + t.Name + "\"");
General.WriteLogLine(ex.InnerException.GetType().Name + ": " + ex.InnerException.Message);
throw;
}
catch(Exception ex)
{
// Error!
General.ErrorLogger.Add(ErrorType.Error, "Failed to create class instance \"" + t.Name + "\"");
General.WriteLogLine(ex.GetType().Name + ": " + ex.Message);
throw;
}
// Add to list
if(checker != null)
{
checkers.Add(checker);
totalprogress += checker.TotalProgress;
}
}
}
// Sort the checkers with highest cost first
// See CompareTo method in ErrorChecker for sorting comparison
checkers.Sort();
// Setup
SetProgressMaximum(totalprogress);
// Continue while threads are running or checks are to be done
while((nextchecker < checkers.Count) || (threads.Count > 0))
{
// Start new thread when less than maximum number of
// threads running and there is more work to be done
while((threads.Count < maxthreads) && (nextchecker < checkers.Count))
{
ErrorChecker c = checkers[nextchecker++];
Thread t = new Thread(c.Run);
t.Name = "Error Checker '" + c.GetType().Name + "'";
t.Priority = ThreadPriority.BelowNormal;
t.Start();
threads.Add(t);
}
// Remove threads that are done
for(int i = threads.Count - 1; i >= 0; i--)
if(!threads[i].IsAlive) threads.RemoveAt(i);
// Handle thread interruption
try { Thread.Sleep(1); }
catch(ThreadInterruptedException) { break; }
}
// Stop all running threads
foreach(Thread t in threads)
{
while(t.IsAlive)
{
try
{
t.Interrupt();
t.Join(1);
}
catch(ThreadInterruptedException)
{
// We have to continue, we can't just leave the other threads running!
}
}
}
// Done
StopChecking();
}
//mxd
private Dictionary<Type, bool> GetSelectedTypes()
{
Dictionary<Type, bool> selectedtypes = new Dictionary<Type, bool>();
foreach(var ro in results.SelectedItems)
{
ErrorResult r = ro as ErrorResult;
if(r == null) continue;
Type t = r.GetType();
if(!selectedtypes.ContainsKey(t)) selectedtypes.Add(t, false);
}
return selectedtypes;
}
#endregion
#region ================== Events
// Window closing
private void ErrorCheckForm_FormClosing(object sender, FormClosingEventArgs e)
{
//mxd. Clear results
resultslist.Clear();
results.Items.Clear();
// If the user closes the form, then just cancel the mode
if(e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
General.Interface.Focus();
General.Editing.CancelMode();
}
}
// Start/stop
private void buttoncheck_Click(object sender, EventArgs e)
{
// Currently running?
if(running)
{
Cursor.Current = Cursors.WaitCursor;
checksthread.Interrupt();
}
else
{
StartChecking();
}
}
// Close
private void closebutton_Click(object sender, EventArgs e)
{
General.Interface.Focus();
General.Editing.CancelMode();
}
// Results selection changed
private void results_SelectedIndexChanged(object sender, EventArgs e)
{
//mxd
if(bathselectioninprogress) return;
// Anything selected?
if(results.SelectedItems.Count > 0)
{
ErrorResult firstresult = (results.SelectedItems[0] as ErrorResult);
if(firstresult == null)
{
ClearSelectedResult();
}
else
{
bool sametype = true;
List<ErrorResult> validresults = new List<ErrorResult>();
// Selected results have the same fixes?
foreach(var ri in results.SelectedItems)
{
ErrorResult result = ri as ErrorResult;
if(result == null) continue;
validresults.Add(result);
if(result.Buttons != firstresult.Buttons || result.Button1Text != firstresult.Button1Text
|| result.Button2Text != firstresult.Button2Text || result.Button3Text != firstresult.Button3Text)
{
sametype = false;
break;
}
}
resultinfo.Enabled = true;
if(sametype)
{
resultinfo.Text = firstresult.Description;
fix1.Text = firstresult.Button1Text;
fix2.Text = firstresult.Button2Text;
fix3.Text = firstresult.Button3Text;
fix1.Visible = (firstresult.Buttons > 0);
fix2.Visible = (firstresult.Buttons > 1);
fix3.Visible = (firstresult.Buttons > 2);
}
else
{
resultinfo.Text = "Several types of map analysis results are selected. To display fixes, make sure that only a single result type is selected.";
fix1.Visible = false;
fix2.Visible = false;
fix3.Visible = false;
}
// Zoom to area
if(validresults.Count > 0)
{
RectangleF zoomarea = validresults[0].GetZoomArea();
foreach(ErrorResult result in validresults)
{
zoomarea = RectangleF.Union(zoomarea, result.GetZoomArea());
}
ClassicMode editmode = (General.Editing.Mode as ClassicMode);
editmode.CenterOnArea(zoomarea, 0.6f);
}
}
UpdateTitle(); //mxd
}
else
{
ClearSelectedResult();
}
General.Interface.RedrawDisplay();
}
// First button
private void fix1_Click(object sender, EventArgs e)
{
// Anything selected?
if(results.SelectedItems.Count > 0)
{
if(running)
{
General.ShowWarningMessage("You must stop the analysis before you can make changes to your map!", MessageBoxButtons.OK);
}
else
{
ErrorResult r = (results.SelectedItem as ErrorResult);
if(r.Button1Click(false))
{
if(results.SelectedItems.Count > 1) FixSimilarErrors(r.GetType(), 1); //mxd
StartChecking();
}
else
{
General.Interface.RedrawDisplay();
}
}
}
}
// Second button
private void fix2_Click(object sender, EventArgs e)
{
// Anything selected?
if(results.SelectedIndex >= 0)
{
if(running)
{
General.ShowWarningMessage("You must stop the analysis before you can make changes to your map!", MessageBoxButtons.OK);
}
else
{
ErrorResult r = (results.SelectedItem as ErrorResult);
if(r.Button2Click(false))
{
if(results.SelectedItems.Count > 1) FixSimilarErrors(r.GetType(), 2); //mxd
StartChecking();
}
else
{
General.Interface.RedrawDisplay();
}
}
}
}
// Third button
private void fix3_Click(object sender, EventArgs e)
{
// Anything selected?
if(results.SelectedIndex >= 0)
{
if(running)
{
General.ShowWarningMessage("You must stop the analysis before you can make changes to your map!", MessageBoxButtons.OK);
}
else
{
ErrorResult r = (results.SelectedItem as ErrorResult);
if(r.Button3Click(false))
{
if(results.SelectedItems.Count > 1) FixSimilarErrors(r.GetType(), 3); //mxd
StartChecking();
}
else
{
General.Interface.RedrawDisplay();
}
}
}
}
//mxd
private void FixSimilarErrors(Type type, int fixIndex)
{
foreach(Object item in results.SelectedItems)
{
if(item == results.SelectedItem) continue;
if(item.GetType() != type) continue;
ErrorResult r = item as ErrorResult;
if(fixIndex == 1 && !r.Button1Click(true)) break;
if(fixIndex == 2 && !r.Button2Click(true)) break;
if(fixIndex == 3 && !r.Button3Click(true)) break;
}
}
//mxd
private void toggleall_CheckedChanged(object sender, EventArgs e)
{
foreach(CheckBox cb in checks.Checkboxes) cb.Checked = toggleall.Checked;
}
private void ErrorCheckForm_HelpRequested(object sender, HelpEventArgs hlpevent)
{
General.ShowHelp("e_mapanalysis.html");
}
#endregion
#region ================== Results Context Menu (mxd)
private void resultcontextmenustrip_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
//disable or enable stuff
bool haveresult = resultslist.Count > 0 && results.SelectedItems.Count > 0;
resultshowall.Enabled = (resultslist.Count > 0 && resultslist.Count > results.Items.Count);
resultselectcurrenttype.Enabled = haveresult;
resultcopytoclipboard.Enabled = haveresult;
resulthidecurrent.Enabled = haveresult;
resulthidecurrenttype.Enabled = haveresult;
resultshowonlycurrent.Enabled = haveresult;
}
private void resultshowall_Click(object sender, EventArgs e)
{
// Reset ignored items
foreach(ErrorResult result in resultslist) result.Hide(false);
// Restore items
results.Items.Clear();
results.Items.AddRange(resultslist.ToArray());
hiddentresulttypes.Clear();
// Do the obvious
ClearSelectedResult();
}
private void resulthidecurrent_Click(object sender, EventArgs e)
{
// Collect results to hide
List<ErrorResult> tohide = new List<ErrorResult>();
foreach(var ro in results.SelectedItems)
{
ErrorResult r = ro as ErrorResult;
if(r == null) return;
r.Hide(true);
tohide.Add(r);
}
// Remove from the list
results.BeginUpdate();
foreach(ErrorResult r in tohide) results.Items.Remove(r);
results.EndUpdate();
// Do the obvious
ClearSelectedResult();
}
private void resulthidecurrenttype_Click(object sender, EventArgs e)
{
Dictionary<Type, bool> tohide = GetSelectedTypes();
List<ErrorResult> filtered = new List<ErrorResult>();
hiddentresulttypes.AddRange(tohide.Keys);
// Apply filtering
foreach(ErrorResult result in results.Items)
{
if(!tohide.ContainsKey(result.GetType())) filtered.Add(result);
}
// Replace items
results.Items.Clear();
results.Items.AddRange(filtered.ToArray());
// Do the obvious
ClearSelectedResult();
}
private void resultshowonlycurrent_Click(object sender, EventArgs e)
{
Dictionary<Type, bool> toshow = GetSelectedTypes();
List<ErrorResult> filtered = new List<ErrorResult>();
hiddentresulttypes.Clear();
// Apply filtering
foreach(ErrorResult result in results.Items)
{
Type curresulttype = result.GetType();
if(!toshow.ContainsKey(curresulttype))
{
hiddentresulttypes.Add(curresulttype);
}
else
{
filtered.Add(result);
}
}
// Replace items
results.Items.Clear();
results.Items.AddRange(filtered.ToArray());
// Do the obvious
ClearSelectedResult();
}
private void resultcopytoclipboard_Click(object sender, EventArgs e)
{
// Get results
StringBuilder sb = new StringBuilder();
foreach(ErrorResult result in results.SelectedItems) sb.AppendLine(result.ToString());
// Set on clipboard
Clipboard.SetDataObject(sb.ToString(), true, 5, 200); //mxd
// Inform the user
General.Interface.DisplayStatus(StatusType.Info, "Analysis results copied to clipboard.");
}
private void results_KeyUp(object sender, KeyEventArgs e)
{
// Copy descriptions to clipboard?
if(e.Control && e.KeyCode == Keys.C)
{
resultcopytoclipboard_Click(sender, EventArgs.Empty);
}
// Select all?
else if(e.Control && e.KeyCode == Keys.A)
{
results.SelectedItems.Clear();
bathselectioninprogress = true; //mxd
results.BeginUpdate(); //mxd
for(int i = 0; i < results.Items.Count; i++) results.SelectedItems.Add(results.Items[i]);
results.EndUpdate(); //mxd
bathselectioninprogress = false; //mxd
results_SelectedIndexChanged(this, EventArgs.Empty); //trigger update manually
}
}
private void resultselectcurrenttype_Click(object sender, EventArgs e)
{
Dictionary<Type, bool> toselect = GetSelectedTypes();
results.SelectedItems.Clear();
bathselectioninprogress = true; //mxd
results.BeginUpdate(); //mxd
for(int i = 0; i < results.Items.Count; i++)
{
if(toselect.ContainsKey(results.Items[i].GetType())) results.SelectedItems.Add(results.Items[i]);
}
results.EndUpdate(); //mxd
bathselectioninprogress = false; //mxd
results_SelectedIndexChanged(this, EventArgs.Empty); //trigger update manually
}
#endregion
}
}