UltimateZoneBuilder/Source/Core/Controls/FieldsEditorControl.cs
MaxED 5cb573d938 GZDoom Builder 1.11:
Enhanced scripting workflow.
Thing and linedef arguments can now have default value in configuration files.
Fixed laggy visual camera movement on systems with big uptime.
Fixed incorrect alignment of Things with "hangs" flag in GZDoom Visual mode.
Fixed Editor crash when nodebuilder fails to build map. A window with error description will be shown instead.
Doom light levels were used even in maps in non-doom map format.

Tag Explorer plugin:
Elements list wasn't updated when map element was deleted.
Pugin wasn't disposed properly, which may led to Doom Builder 2 crash on map close.
Fixed plugin crash after opening another map.
Fixed unresponsive text entry in Filter text box.
Fixed plugin crash when Doom Builder can't determmine thing category ("UNKNOWN" category will be used instead).
2012-07-10 10:20:45 +00:00

868 lines
24 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.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Diagnostics;
using CodeImp.DoomBuilder.Actions;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.Rendering;
using SlimDX.Direct3D9;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using CodeImp.DoomBuilder.Map;
using System.Globalization;
using CodeImp.DoomBuilder.Types;
#endregion
namespace CodeImp.DoomBuilder.Controls
{
/// <summary>
/// Control to view and/or change custom UDMF fields.
/// </summary>
public partial class FieldsEditorControl : UserControl
{
#region ================== Constants
// Constants
private const string ADD_FIELD_TEXT = " (click to add custom field)";
private const string FIELD_PREFIX_SUGGESTION = "user_";
#endregion
#region ================== Variables
public delegate void SingleFieldNameEvent(string fieldname);
public delegate void DualFieldNameEvent(string oldname, string newname);
// Events
public event SingleFieldNameEvent OnFieldInserted;
public event DualFieldNameEvent OnFieldNameChanged;
public event SingleFieldNameEvent OnFieldValueChanged;
public event SingleFieldNameEvent OnFieldTypeChanged;
public event SingleFieldNameEvent OnFieldDeleted;
public event SingleFieldNameEvent OnFieldUndefined;
// Variables
private string elementname;
private string lasteditfieldname;
private bool autoinsertuserprefix;
#endregion
#region ================== Properties
public bool AllowInsert { get { return fieldslist.AllowUserToAddRows; } set { fieldslist.AllowUserToAddRows = value; SetupNewRowStyle(); } }
public bool AutoInsertUserPrefix { get { return autoinsertuserprefix; } set { autoinsertuserprefix = value; } }
public int PropertyColumnWidth { get { return fieldname.Width; } set { fieldname.Width = value; UpdateValueColumn(); UpdateBrowseButton(); } }
public int TypeColumnWidth { get { return fieldtype.Width; } set { fieldtype.Width = value; UpdateValueColumn(); UpdateBrowseButton(); } }
public bool PropertyColumnVisible { get { return fieldname.Visible; } set { fieldname.Visible = value; UpdateValueColumn(); UpdateBrowseButton(); } }
public bool TypeColumnVisible { get { return fieldtype.Visible; } set { fieldtype.Visible = value; UpdateValueColumn(); UpdateBrowseButton(); } }
public bool ValueColumnVisible { get { return fieldvalue.Visible; } set { fieldvalue.Visible = value; UpdateValueColumn(); UpdateBrowseButton(); } }
#endregion
#region ================== Constructor
// Constructor
public FieldsEditorControl()
{
InitializeComponent();
autoinsertuserprefix = true;
enumscombo.Location = new Point(-1000, 1);
}
#endregion
#region ================== Setup / Apply
// This sets up the control
public void Setup(string elementname)
{
// Keep element name
this.elementname = elementname;
// Make types list
fieldtype.Items.Clear();
fieldtype.Items.AddRange(General.Types.GetCustomUseAttributes());
}
// This applies last sort order
private void Sort()
{
// Sort
int sortcolumn = General.Settings.ReadSetting("customfieldssortcolumn", 0);
int sortorder = General.Settings.ReadSetting("customfieldssortorder", (int)ListSortDirection.Ascending);
if(sortorder == (int)SortOrder.Ascending)
fieldslist.Sort(fieldslist.Columns[sortcolumn], ListSortDirection.Ascending);
else if(sortorder == (int)SortOrder.Descending)
fieldslist.Sort(fieldslist.Columns[sortcolumn], ListSortDirection.Descending);
}
// This adds a list of fixed fields (in undefined state)
public void ListFixedFields(List<UniversalFieldInfo> list)
{
// Add all fields
foreach(UniversalFieldInfo uf in list)
fieldslist.Rows.Add(new FieldsEditorRow(fieldslist, uf));
// Sort fields
Sort();
// Update new row
SetupNewRowStyle();
}
// Use this in case you don't want the fixed fields
public void ListNoFixedFields()
{
// Update new row
SetupNewRowStyle();
}
// Clear all fields
public void ClearFields()
{
// Trash rows
fieldslist.Rows.Clear();
// Update new row
SetupNewRowStyle();
}
// This sets up the fields and values from a UniFields object
// When first is true, the values are applied unconditionally
// When first is false, the values in the grid are erased when
// they differ from the given values (for multiselection)
public void SetValues(UniFields fromfields, bool first)
{
// Go for all the fields
foreach(KeyValuePair<string, UniValue> f in fromfields)
{
// Go for all rows
bool foundrow = false;
foreach(DataGridViewRow row in fieldslist.Rows)
{
// Row is a field?
if(row is FieldsEditorRow)
{
FieldsEditorRow frow = row as FieldsEditorRow;
// Row name matches with field
if(frow.Name == f.Key)
{
// First time?
if(first)
{
// Set type when row is not fixed
if(!frow.IsFixed) frow.ChangeType(f.Value.Type);
// Apply value of field to row
frow.Define(f.Value.Value);
}
else
{
// Check if the value is different
if(!frow.TypeHandler.GetValue().Equals(f.Value.Value))
{
// Clear the value in the row
frow.Define(f.Value.Value);
frow.Clear();
}
}
// Done
foundrow = true;
break;
}
}
}
// Row not found?
if(!foundrow)
{
// Make new row
FieldsEditorRow frow = new FieldsEditorRow(fieldslist, f.Key, f.Value.Type, f.Value.Value);
fieldslist.Rows.Insert(fieldslist.Rows.Count - 1, frow);
// When not the first, clear the field
// because the others did not define this one
if(!first) frow.Clear();
}
}
// Now check for rows that the givens fields do NOT have
foreach(DataGridViewRow row in fieldslist.Rows)
{
// Row is a field?
if(row is FieldsEditorRow)
{
FieldsEditorRow frow = row as FieldsEditorRow;
// Is this row defined previously?
if(frow.IsDefined)
{
// Check if this row can not be found in the fields at all
if(!fromfields.ContainsKey(frow.Name))
{
// It is not defined in these fields, clear the value
frow.Clear();
}
}
}
}
// Sort fields
Sort();
}
//mxd
public object GetValue(string name) {
//have required row?
foreach (DataGridViewRow row in fieldslist.Rows) {
// Row is a field?
if (row is FieldsEditorRow) {
FieldsEditorRow frow = row as FieldsEditorRow;
// Row name matches with field
if (frow.Name == name) {
// Apply value of field to row
if (frow.IsDefined && !frow.IsEmpty)
return frow.GetResult(null);
return null;
}
}
}
return null;
}
//mxd
public void SetValue(string name, object value, UniversalType type) {
//have required row?
foreach (DataGridViewRow row in fieldslist.Rows) {
// Row is a field?
if (row is FieldsEditorRow) {
FieldsEditorRow frow = row as FieldsEditorRow;
// Row name matches with field
if (frow.Name == name) {
// Apply value of field to row
frow.Define(value);
return;
}
}
}
//no such row... let's add it
FieldsEditorRow newfrow = new FieldsEditorRow(fieldslist, name, (int)type, value);
fieldslist.Rows.Insert(fieldslist.Rows.Count - 1, newfrow);
}
// This applies the current fields to a UniFields object
public void Apply(UniFields tofields)
{
tofields.BeforeFieldsChange();
// Go for all the fields
UniFields tempfields = new UniFields(tofields);
foreach(KeyValuePair<string, UniValue> f in tempfields)
{
// Go for all rows
bool foundrow = false;
foreach(DataGridViewRow row in fieldslist.Rows)
{
// Row is a field and matches field name?
if((row is FieldsEditorRow) && (row.Cells[0].Value.ToString() == f.Key))
{
FieldsEditorRow frow = row as FieldsEditorRow;
// Field is defined?
if(frow.IsDefined)
{
foundrow = true;
break;
}
}
}
// No such row?
if(!foundrow)
{
// Remove the definition from the fields
tofields.Remove(f.Key);
}
}
// Go for all rows
foreach(DataGridViewRow row in fieldslist.Rows)
{
// Row is a field?
if(row is FieldsEditorRow)
{
FieldsEditorRow frow = row as FieldsEditorRow;
// Field is defined and not empty?
if(frow.IsDefined && !frow.IsEmpty)
{
// Apply field
object oldvalue = null;
if(tofields.ContainsKey(frow.Name)) oldvalue = tofields[frow.Name].Value;
tofields[frow.Name] = new UniValue(frow.TypeHandler.Index, frow.GetResult(oldvalue));
// Custom row?
if(!frow.IsFixed)
{
// Write type to map configuration
General.Map.Options.SetUniversalFieldType(elementname, frow.Name, frow.TypeHandler.Index);
}
}
}
}
}
#endregion
#region ================== Events
// Column header clicked
private void fieldslist_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
// Save sort order
if(fieldslist.SortedColumn != null)
{
int sortcolumn = fieldslist.SortedColumn.Index;
int sortorder = (int)fieldslist.SortOrder;
General.Settings.WriteSetting("customfieldssortcolumn", sortcolumn);
General.Settings.WriteSetting("customfieldssortorder", sortorder);
}
// Stop any cell editing
ApplyEnums(true);
fieldslist.EndEdit();
HideBrowseButton();
}
// Resized
private void FieldsEditorControl_Resize(object sender, EventArgs e)
{
// Rearrange controls
fieldslist.Size = this.ClientSize;
UpdateValueColumn();
UpdateBrowseButton();
}
// Layout change
private void FieldsEditorControl_Layout(object sender, LayoutEventArgs e)
{
FieldsEditorControl_Resize(sender, EventArgs.Empty);
}
// Cell clicked
private void fieldslist_CellClick(object sender, DataGridViewCellEventArgs e)
{
ApplyEnums(true);
// Anything selected
if(fieldslist.SelectedCells.Count > 0)
{
if(e.RowIndex == fieldslist.NewRowIndex)
fieldslist.BeginEdit(false);
else if(e.ColumnIndex > 0)
fieldslist.BeginEdit(true);
}
}
// Cell doubleclicked
private void fieldslist_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
FieldsEditorRow frow = null;
// Anything selected
if(fieldslist.SelectedRows.Count > 0 && e.RowIndex > -1)
{
// Get the row
DataGridViewRow row = fieldslist.Rows[e.RowIndex];
if(row is FieldsEditorRow) frow = row as FieldsEditorRow;
// First column?
if(e.ColumnIndex == 0)
{
// Not a fixed field?
if((frow != null) && !frow.IsFixed)
{
lasteditfieldname = frow.Name;
fieldslist.CurrentCell = fieldslist.SelectedRows[0].Cells[0];
fieldslist.CurrentCell.ReadOnly = false;
if((e.RowIndex == fieldslist.NewRowIndex) ||
frow.Name.StartsWith(FIELD_PREFIX_SUGGESTION, true, CultureInfo.InvariantCulture))
fieldslist.BeginEdit(false);
else
fieldslist.BeginEdit(true);
}
}
}
}
// User deletes a row
private void fieldslist_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e)
{
// Get the row
FieldsEditorRow row = e.Row as FieldsEditorRow;
// Fixed field?
if(row.IsFixed)
{
// Just undefine the field
row.Undefine();
e.Cancel = true;
if(OnFieldUndefined != null)
OnFieldUndefined(row.Name);
}
else
{
if(OnFieldDeleted != null)
OnFieldDeleted(row.Name);
}
}
// User selects a cell for editing
private void fieldslist_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
{
// Field name cell?
if(e.ColumnIndex == 0)
{
// New row index?
if(e.RowIndex == fieldslist.NewRowIndex)
{
// Remove all text
fieldslist.Rows[e.RowIndex].Cells[0].Style.ForeColor = SystemColors.WindowText;
if(autoinsertuserprefix)
fieldslist.Rows[e.RowIndex].Cells[0].Value = FIELD_PREFIX_SUGGESTION;
else
fieldslist.Rows[e.RowIndex].Cells[0].Value = "";
}
}
// Value cell?
else if(e.ColumnIndex == 2)
{
// Get the row
FieldsEditorRow frow = null;
DataGridViewRow row = fieldslist.Rows[e.RowIndex];
if(row is FieldsEditorRow)
{
// Get specializedrow
frow = row as FieldsEditorRow;
// Enumerable?
if(frow.TypeHandler.IsEnumerable)
{
// Fill combo with enums
enumscombo.SelectedItem = null;
enumscombo.Text = "";
enumscombo.Items.Clear();
enumscombo.Items.AddRange(frow.TypeHandler.GetEnumList().ToArray());
enumscombo.Tag = frow;
// Lock combo to enums?
if(frow.TypeHandler.IsLimitedToEnums)
enumscombo.DropDownStyle = ComboBoxStyle.DropDownList;
else
enumscombo.DropDownStyle = ComboBoxStyle.DropDown;
// Position combobox
Rectangle cellrect = fieldslist.GetCellDisplayRectangle(2, row.Index, false);
enumscombo.Location = new Point(cellrect.Left, cellrect.Top);
enumscombo.Width = cellrect.Width;
int internalheight = cellrect.Height - (enumscombo.Height - enumscombo.ClientRectangle.Height) - 6;
General.SendMessage(enumscombo.Handle, General.CB_SETITEMHEIGHT, -1, internalheight);
// Select the value of this field (for DropDownList style combo)
foreach(EnumItem i in enumscombo.Items)
{
// Matches?
if(string.Compare(i.Title, frow.TypeHandler.GetStringValue(), true, CultureInfo.InvariantCulture) == 0)
{
// Select this item
enumscombo.SelectedItem = i;
}
}
// Put the display text in the text (for DropDown style combo)
enumscombo.Text = frow.TypeHandler.GetStringValue();
// Show combo
enumscombo.Show();
}
}
}
}
// Done editing cell contents
private void fieldslist_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
FieldsEditorRow frow = null;
DataGridViewRow row = null;
// Get the row
row = fieldslist.Rows[e.RowIndex];
if(row is FieldsEditorRow) frow = row as FieldsEditorRow;
// Renaming a field?
if(e.ColumnIndex == 0)
{
// Row is a new row?
if(frow == null)
{
// Name given?
if((row.Cells[0].Value != null) && (row.Cells[0].Value.ToString() != FIELD_PREFIX_SUGGESTION))
{
// Make a valid UDMF field name
string validname = UniValue.ValidateName(row.Cells[0].Value.ToString());
if(validname.Length > 0)
{
// Check if no other row already has this name
foreach(DataGridViewRow r in fieldslist.Rows)
{
// Name matches and not the same row?
if((r.Index != row.Index) && (r.Cells.Count > 0) && (r.Cells[0].Value != null) &&
(r.Cells[0].Value.ToString().ToLowerInvariant() == validname))
{
// Cannot have two rows with same name
validname = "";
General.ShowWarningMessage("Fields must have unique names!", MessageBoxButtons.OK);
break;
}
}
// Still valid?
if(validname.Length > 0)
{
// Try to find the type in the map options
int type = General.Map.Options.GetUniversalFieldType(elementname, validname, 0);
// Make new row
frow = new FieldsEditorRow(fieldslist, validname, type, null);
frow.Visible = false;
fieldslist.Rows.Insert(e.RowIndex + 1, frow);
if(OnFieldInserted != null)
OnFieldInserted(validname);
}
}
}
// Mark the row for delete
row.ReadOnly = true;
deleterowstimer.Start();
}
else
{
// Name given?
if(row.Cells[0].Value != null)
{
// Make a valid UDMF field name
string validname = UniValue.ValidateName(row.Cells[0].Value.ToString());
if(validname.Length > 0)
{
// Check if no other row already has this name
foreach(DataGridViewRow r in fieldslist.Rows)
{
// Name matches and not the same row?
if((r.Index != row.Index) && (r.Cells.Count > 0) && (r.Cells[0].Value != null) &&
(r.Cells[0].Value.ToString().ToLowerInvariant() == validname))
{
// Cannot have two rows with same name
validname = "";
row.Cells[0].Value = lasteditfieldname;
General.ShowWarningMessage("Fields must have unique names!", MessageBoxButtons.OK);
break;
}
}
// Still valid?
if(validname.Length > 0)
{
// Try to find the type in the map options
int type = General.Map.Options.GetUniversalFieldType(elementname, validname, -1);
// Rename row and change type
row.Cells[0].Value = validname;
if(type != -1) frow.ChangeType(type);
if(OnFieldNameChanged != null)
OnFieldNameChanged(lasteditfieldname, validname);
if(OnFieldTypeChanged != null)
OnFieldTypeChanged(validname);
}
else
{
// Keep old name
row.Cells[0].Value = lasteditfieldname;
}
}
else
{
// Keep old name
row.Cells[0].Value = lasteditfieldname;
}
}
else
{
// Keep old name
row.Cells[0].Value = lasteditfieldname;
}
}
}
// Changing field type?
if((e.ColumnIndex == 1) && (frow != null))
{
if(OnFieldTypeChanged != null)
OnFieldTypeChanged(frow.Name);
}
// Changing field value?
if((e.ColumnIndex == 2) && (frow != null))
{
// Apply changes
ApplyValue(frow, row.Cells[2].Value);
}
// Updated
if(frow != null) frow.CellChanged();
// Update button
UpdateBrowseButton();
}
// Time to delete rows
private void deleterowstimer_Tick(object sender, EventArgs e)
{
// Stop timer
deleterowstimer.Stop();
// Delete all rows that must be deleted
for(int i = fieldslist.Rows.Count - 1; i >= 0; i--)
{
if(fieldslist.Rows[i].ReadOnly)
try { fieldslist.Rows.RemoveAt(i); } catch(Exception) { }
else
fieldslist.Rows[i].Visible = true;
}
// Update new row
SetupNewRowStyle();
// Update button
UpdateBrowseButton();
}
// Selection changes
private void fieldslist_SelectionChanged(object sender, EventArgs e)
{
browsebutton.Visible = false;
ApplyEnums(true);
// Update button
UpdateBrowseButton();
}
// Browse clicked
private void browsebutton_Click(object sender, EventArgs e)
{
// Any row selected?
if(fieldslist.SelectedRows.Count > 0)
{
// Get selected row
DataGridViewRow row = fieldslist.SelectedRows[0];
if(row is FieldsEditorRow)
{
// Browse
(row as FieldsEditorRow).Browse(this.ParentForm);
fieldslist.Focus();
}
}
}
// This handles field data errors
private void fieldslist_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
// Ignore this, because we want to display values
// in the type column that are not in their combobox
e.ThrowException = false;
}
// Validate value in enums combobox
private void enumscombo_Validating(object sender, CancelEventArgs e)
{
ApplyEnums(false);
}
// Scrolling
private void fieldslist_Scroll(object sender, ScrollEventArgs e)
{
// Stop any cell editing
ApplyEnums(true);
fieldslist.EndEdit();
HideBrowseButton();
}
// Mouse up event
private void fieldslist_MouseUp(object sender, MouseEventArgs e)
{
// Focus to enums combobox when visible
if(enumscombo.Visible)
{
enumscombo.Focus();
enumscombo.SelectAll();
}
}
#endregion
#region ================== Private Methods
// This applies a value to a row
private void ApplyValue(FieldsEditorRow frow, object value)
{
// Defined?
if((value != null) && (!frow.IsFixed || !frow.Info.Default.Equals(value)))
{
frow.Define(value);
}
else if(frow.IsFixed)
{
frow.Undefine();
}
if(OnFieldValueChanged != null)
OnFieldValueChanged(frow.Name);
}
// This applies the contents of the enums combobox and hides (if opened)
private void ApplyEnums(bool hide)
{
// Enums combobox shown?
if((enumscombo.Visible) && (enumscombo.Tag is FieldsEditorRow))
{
// Get the row
FieldsEditorRow frow = (enumscombo.Tag as FieldsEditorRow);
// Take the selected value and apply it
ApplyValue(frow, enumscombo.Text);
// Updated
frow.CellChanged();
}
if(hide)
{
// Hide combobox
enumscombo.Tag = null;
enumscombo.Visible = false;
enumscombo.Items.Clear();
}
}
// This sets up the new row
private void SetupNewRowStyle()
{
if(fieldslist.AllowUserToAddRows)
{
// Show text for new row
fieldslist.Rows[fieldslist.NewRowIndex].Cells[0].Value = ADD_FIELD_TEXT;
fieldslist.Rows[fieldslist.NewRowIndex].Cells[0].Style.ForeColor = SystemColors.GrayText;
fieldslist.Rows[fieldslist.NewRowIndex].Cells[0].ReadOnly = false;
// Make sure user can only enter property name in a new row
fieldslist.Rows[fieldslist.NewRowIndex].Cells[1].ReadOnly = true;
fieldslist.Rows[fieldslist.NewRowIndex].Cells[2].ReadOnly = true;
}
}
// This hides the browse button
private void HideBrowseButton()
{
browsebutton.Visible = false;
}
// This updates the Value column width
private void UpdateValueColumn()
{
int fieldnamewidth = fieldname.Visible ? fieldname.Width : 0;
int fieldtypewidth = fieldtype.Visible ? fieldtype.Width : 0;
fieldvalue.Width = fieldslist.ClientRectangle.Width - fieldnamewidth - fieldtypewidth - SystemInformation.VerticalScrollBarWidth - 10;
}
// This updates the button
private void UpdateBrowseButton()
{
FieldsEditorRow frow = null;
DataGridViewRow row = null;
// Any row selected?
if(fieldslist.SelectedRows.Count > 0)
{
// Get selected row
row = fieldslist.SelectedRows[0];
if(row is FieldsEditorRow) frow = row as FieldsEditorRow;
// Not the new row and FieldsEditorRow available?
if((row.Index < fieldslist.NewRowIndex) && (frow != null))
{
// Browse button available for this type?
if(frow.TypeHandler.IsBrowseable && !frow.TypeHandler.IsEnumerable)
{
Rectangle cellrect = fieldslist.GetCellDisplayRectangle(2, row.Index, false);
// Show button
enumscombo.Visible = false;
browsebutton.Image = frow.TypeHandler.BrowseImage;
browsebutton.Location = new Point(cellrect.Right - browsebutton.Width, cellrect.Top);
browsebutton.Height = cellrect.Height;
Console.WriteLine(cellrect.Height.ToString());
browsebutton.Visible = true;
}
else
{
HideBrowseButton();
}
}
else
{
HideBrowseButton();
}
}
else
{
HideBrowseButton();
}
}
#endregion
}
}