This commit is contained in:
Matt Neumann 2025-03-10 18:34:45 -07:00
parent 33f0e5b1c7
commit 8dc217116f
3 changed files with 1850 additions and 139 deletions

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace Better_NCP_Editor
@ -9,49 +10,58 @@ namespace Better_NCP_Editor
{
public object NewValue { get; private set; }
private Control inputControl;
private readonly Type valueType;
// Constructor now takes an extra parameter: parentNodeName.
public EditValueForm(string propertyName, string currentValue, Type valueType, List<string>? comboItems,Dictionary<String,String> allItems)
public EditValueForm(string propertyName, string currentValue, Type valueType, List<string>? comboItems, Dictionary<string, string> allItems, Dictionary<string, UInt64> skinIDs)
{
// Validate required parameters.
if (string.IsNullOrWhiteSpace(propertyName))
throw new ArgumentException("Property name cannot be null or empty.", nameof(propertyName));
if (currentValue == null)
throw new ArgumentNullException(nameof(currentValue));
if (valueType == null)
throw new ArgumentNullException(nameof(valueType));
this.valueType = valueType;
// Set up the form's basic properties and size.
InitializeForm(propertyName, currentValue, comboItems);
// Create and add the input control.
CreateInputControl(propertyName, currentValue, comboItems, allItems, skinIDs);
// Create and add OK/Cancel buttons.
CreateButtons();
}
private void InitializeForm(string propertyName, string currentValue, List<string>? comboItems)
{
// Set the minimum and default size.
this.MinimumSize = new Size(210, 120);
//this.AutoScaleDimensions = new SizeF(96F, 96F); // or your base DPI
this.AutoScaleMode = AutoScaleMode.None;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.StartPosition = FormStartPosition.CenterParent;
this.Text = $"Edit {propertyName}";
// Measure the width of the property name.
// Measure text widths for layout.
int propertyNameWidth = TextRenderer.MeasureText(propertyName, this.Font).Width;
// For non-bool types, measure the current value width.
int currentValueWidth = valueType == typeof(bool)
? TextRenderer.MeasureText("false", this.Font).Width + 30
: TextRenderer.MeasureText(currentValue, this.Font).Width + 40;
// If combo items are provided, determine the maximum width needed for them.
int comboItemsWidth = 0;
if (comboItems != null)
{
foreach (var item in comboItems)
{
int itemWidth = TextRenderer.MeasureText(item, this.Font).Width;
if (itemWidth > comboItemsWidth)
comboItemsWidth = itemWidth;
comboItemsWidth = Math.Max(comboItemsWidth, itemWidth);
}
// Add padding for the dropdown arrow and some extra margin.
comboItemsWidth += 20;
comboItemsWidth += 20; // extra padding for dropdown arrow and margin.
}
// Determine the desired width by taking the maximum of the measured values and adding extra margins.
int desiredWidth = Math.Max(propertyNameWidth, Math.Max(currentValueWidth, comboItemsWidth)) + 40;
// Set the client size based on the desired width.
this.ClientSize = new Size(desiredWidth, 120);
this.Text = $"Edit {propertyName}";
this.StartPosition = FormStartPosition.CenterParent;
// Create the property label.
// Create and add the property label.
Label lblProperty = new Label()
{
Text = propertyName,
@ -59,59 +69,60 @@ namespace Better_NCP_Editor
AutoSize = true
};
this.Controls.Add(lblProperty);
}
int controlWidth = desiredWidth - 20;
private void CreateInputControl(string propertyName, string currentValue, List<string>? comboItems, Dictionary<string, string> allItems, Dictionary<string, UInt64> skinIDs)
{
int controlWidth = this.ClientSize.Width - 20;
// Check if we need to use a special ComboBox (for "ShortName" in "Wear items").
if (comboItems != null)
{
ComboBox combo = new ComboBox()
// Create a ComboBox when comboItems are provided.
ComboBox combo = new ComboBox
{
Location = new Point(10, 40),
Width = controlWidth, // initial width based on desiredWidth minus margins
Width = controlWidth,
DropDownStyle = ComboBoxStyle.DropDownList
};
int maxWidth = 0;
foreach (var key in comboItems)
{
combo.Items.Add(key);
// Measure each item's width using the ComboBox's font.
Size itemSize = TextRenderer.MeasureText(key, combo.Font);
if (itemSize.Width > maxWidth)
maxWidth = itemSize.Width;
}
// Optionally add some extra padding.
int paddedWidth = maxWidth + 20;
// Set the DropDownWidth to ensure items are fully visible.
// Add items and adjust the dropdown width to fit the widest item.
int maxItemWidth = comboItems.Select(item => TextRenderer.MeasureText(item, combo.Font).Width).Max();
int paddedWidth = maxItemWidth + 20;
combo.DropDownWidth = paddedWidth;
// Optionally, you can also adjust the combo's Width if you want it to match the dropdown width.
if (combo.Width < paddedWidth)
combo.Width = paddedWidth;
// Set selected item to currentValue if found, otherwise select the first item.
var matchingPair = allItems.FirstOrDefault(x => x.Value == currentValue);
if (matchingPair.Key != null)
foreach (var item in comboItems)
{
combo.SelectedItem = matchingPair.Key;
combo.Items.Add(item);
}
// Attempt to set the selected item using the provided dictionaries.
string? selectedKey = allItems.FirstOrDefault(x => x.Value == currentValue).Key;
// If not found, try using skinIDs (with safe parsing).
if (string.IsNullOrEmpty(selectedKey) && skinIDs != null && UInt64.TryParse(currentValue, out UInt64 parsedValue))
{
selectedKey = skinIDs.FirstOrDefault(x => x.Value == parsedValue).Key;
}
if (!string.IsNullOrEmpty(selectedKey) && combo.Items.Contains(selectedKey))
{
combo.SelectedItem = selectedKey;
}
else if (combo.Items.Count > 0)
{
combo.SelectedIndex = 0;
}
inputControl = combo;
this.Controls.Add(combo);
}
else
{
// Create the input control normally.
// For bool types, use a ComboBox to choose true/false.
if (valueType == typeof(bool))
{
ComboBox combo = new ComboBox()
ComboBox combo = new ComboBox
{
Location = new Point(10, 40),
Width = controlWidth,
@ -119,13 +130,22 @@ namespace Better_NCP_Editor
};
combo.Items.Add("true");
combo.Items.Add("false");
combo.SelectedItem = currentValue;
// Parse the current value safely.
if (bool.TryParse(currentValue, out bool boolVal))
{
combo.SelectedItem = boolVal.ToString().ToLower();
}
else
{
combo.SelectedIndex = 0;
}
inputControl = combo;
this.Controls.Add(combo);
}
else
{
TextBox txtBox = new TextBox()
// Default to a TextBox for other types.
TextBox txtBox = new TextBox
{
Location = new Point(10, 40),
Width = controlWidth,
@ -135,9 +155,12 @@ namespace Better_NCP_Editor
this.Controls.Add(txtBox);
}
}
}
private void CreateButtons()
{
// OK button.
Button btnOk = new Button()
Button btnOk = new Button
{
Text = "OK",
Location = new Point(10, 80),
@ -149,7 +172,7 @@ namespace Better_NCP_Editor
this.Controls.Add(btnOk);
// Cancel button.
Button btnCancel = new Button()
Button btnCancel = new Button
{
Text = "Cancel",
Location = new Point(100, 80),
@ -165,18 +188,31 @@ namespace Better_NCP_Editor
private void BtnOk_Click(object sender, EventArgs e)
{
// If the input control is the special ComboBox, return the dictionary value.
if (inputControl is ComboBox combo && combo.DropDownStyle == ComboBoxStyle.DropDownList &&
combo.Items.Count > 0)
// Determine the new value based on the type of the input control.
if (inputControl is ComboBox combo)
{
NewValue = combo.SelectedItem.ToString();
}
else if (inputControl is ComboBox comboBool)
{
NewValue = comboBool.SelectedItem.ToString() == "true";
if (valueType == typeof(bool))
{
// Parse the selected string to a boolean.
if (bool.TryParse(combo.SelectedItem?.ToString(), out bool boolVal))
{
NewValue = boolVal;
}
else
{
// Handle parsing error as needed.
NewValue = false;
}
}
else
{
// For non-boolean values, return the selected string.
NewValue = combo.SelectedItem?.ToString() ?? string.Empty;
}
}
else if (inputControl is TextBox txt)
{
// If possible, parse to int; otherwise, leave as string.
if (int.TryParse(txt.Text, out int intValue))
NewValue = intValue;
else

View File

@ -273,7 +273,31 @@ namespace Better_NCP_Editor
{
child = new TreeNode(kvp.Key);
}
// Set the tooltip using your existing tooltips dictionary.
child.ToolTipText = toolTips.tips.ContainsKey(kvp.Key) ? toolTips.tips[kvp.Key] : "";
// *** New code: if this property is the SkinID property, try to update its tooltip ***
if (kvp.Key.Equals("SkinID (0 - default)", StringComparison.OrdinalIgnoreCase) &&
obj.ContainsKey("ShortName"))
{
string shortName = obj["ShortName"]?.ToString() ?? "";
if (!string.IsNullOrEmpty(shortName) && _itemShortnameToSkinName != null &&
_itemShortnameToSkinName.ContainsKey(shortName))
{
var skinMap = _itemShortnameToSkinName[shortName];
if (UInt64.TryParse(kvp.Value?.ToString(), out UInt64 skinId))
{
// Reverse lookup: find the key (skin display name) whose value equals the skinId.
string matchingDisplayName = skinMap.FirstOrDefault(pair => pair.Value == skinId).Key;
if (!string.IsNullOrEmpty(matchingDisplayName))
{
child.ToolTipText = matchingDisplayName;
}
}
}
}
// *** End new code ***
child.Tag = kvp.Value;
treeNode.Nodes.Add(child);
PopulateTreeRecursive(kvp.Value, child);
@ -300,6 +324,7 @@ namespace Better_NCP_Editor
}
}
private void entityTreeView_AfterSelect(object sender, TreeViewEventArgs e)
{
if (e.Node != null)
@ -324,11 +349,6 @@ namespace Better_NCP_Editor
{
enableAddDelButtons = false;
}
// You can also add additional conditions for a "preset" node, e.g. by checking text:
// else if (e.Node.Text.StartsWith("Preset", StringComparison.OrdinalIgnoreCase))
// {
// enableButtons = true;
// }
btn_entity_add.Enabled = enableAddDelButtons;
btn_entity_del.Enabled = enableAddDelButtons;
@ -357,91 +377,116 @@ namespace Better_NCP_Editor
string propName = parts[0].Trim();
string currentVal = parts[1].Trim();
string? skinToolTip = null;
Type valueType = GetValueType(currentVal);
// Use simple heuristics to determine the type.
Type valueType = typeof(string);
if (bool.TryParse(currentVal, out bool b))
valueType = typeof(bool);
else if (int.TryParse(currentVal, out int i))
valueType = typeof(int);
String parentNodeName = "Root";
String grandParentNodeName = "Root";
List<String> comboList = null;
if (e.Node.Parent != null)
// Get any additional combo and skin lists based on the node hierarchy.
var (comboList, skinList, itemCurrentVal) = GetComboAndSkinLists(e.Node, propName);
using (EditValueForm editForm = new EditValueForm(propName, currentVal, valueType, comboList, _allItems, skinList))
{
parentNodeName = e.Node.Parent.Text;
if (e.Node.Parent.Parent != null)
if (editForm.ShowDialog() == DialogResult.OK)
{
grandParentNodeName = e.Node.Parent.Parent.Text;
// Populate wear items list
if (propName.Equals("ShortName", StringComparison.OrdinalIgnoreCase) && grandParentNodeName.Equals("Wear items"))
object newVal = editForm.NewValue;
string displayVal = newVal is bool ? newVal.ToString().ToLower() : newVal.ToString();
if (_allItems.ContainsKey(displayVal))
{
newVal = _allItems[displayVal];
displayVal = _allItems[displayVal];
}
else if (!string.IsNullOrEmpty(itemCurrentVal) && _itemShortnameToSkinName.ContainsKey(itemCurrentVal))
{
skinToolTip = displayVal;
newVal = _itemShortnameToSkinName[itemCurrentVal][displayVal];
displayVal = _itemShortnameToSkinName[itemCurrentVal][displayVal].ToString();
}
if (skinToolTip != null)
{
e.Node.ToolTipText = skinToolTip;
}
e.Node.Text = $"{propName}: {displayVal}";
if (e.Node.Tag is JsonNode node)
{
JsonNode newNode = JsonValue.Create(newVal);
node.ReplaceWith(newNode);
e.Node.Tag = newNode;
}
// Mark the file as modified.
fileModified = true;
btn_save.Enabled = true;
}
}
}
/// <summary>
/// Determines the type for the current value using simple heuristics.
/// </summary>
private Type GetValueType(string currentVal)
{
if (bool.TryParse(currentVal, out _))
return typeof(bool);
if (int.TryParse(currentVal, out _))
return typeof(int);
return typeof(string);
}
/// <summary>
/// Retrieves combo list and skin dictionary based on the node's hierarchy and property name.
/// Returns a tuple of (comboList, skinList, itemCurrentVal).
/// </summary>
private (List<string> comboList, Dictionary<string, UInt64> skinList, string itemCurrentVal) GetComboAndSkinLists(TreeNode node, string propName)
{
List<string> comboList = null;
Dictionary<string, UInt64> skinList = null;
string itemCurrentVal = string.Empty;
if (node.Parent == null)
return (comboList, skinList, itemCurrentVal);
string parentNodeName = node.Parent.Text;
string grandParentNodeName = node.Parent.Parent?.Text ?? "Root";
// Mods override.
if (parentNodeName.Equals("Mods", StringComparison.OrdinalIgnoreCase))
{
comboList = _weaponModItems;
}
// Check deeper hierarchy.
else if (node.Parent.Parent != null)
{
if (propName.Equals("ShortName", StringComparison.OrdinalIgnoreCase))
{
if (grandParentNodeName.Equals("Wear items", StringComparison.OrdinalIgnoreCase))
comboList = _wearItems;
}
// Populate belt item list
else if (propName.Equals("ShortName", StringComparison.OrdinalIgnoreCase) && grandParentNodeName.Equals("Belt items"))
{
else if (grandParentNodeName.Equals("Belt items", StringComparison.OrdinalIgnoreCase))
comboList = _beltItems;
}
// Populate belt item list
else if (propName.Equals("ShortName", StringComparison.OrdinalIgnoreCase) && grandParentNodeName.Equals("List of items"))
{
// Populate all items
comboList = new List<String>(_allItems.Keys);
}
// Populate skin ids for wear items
else if (propName.Equals("SkinID (0 - default)",StringComparison.OrdinalIgnoreCase) && grandParentNodeName.Equals("Wear items"))
{
// load the list here
}
// Populate skin ids for belt items
else if (propName.Equals("SkinID (0 - default)", StringComparison.OrdinalIgnoreCase) && grandParentNodeName.Equals("Belt items"))
{
// load the list here
}
else if (grandParentNodeName.Equals("List of items", StringComparison.OrdinalIgnoreCase))
comboList = new List<string>(_allItems.Keys);
}
if (parentNodeName.Equals("Mods"))
else if (propName.Equals("SkinID (0 - default)", StringComparison.OrdinalIgnoreCase))
{
comboList = _weaponModItems;
// Expect node text in the form "Property: Value" from the first child.
if (node.Parent.FirstNode != null)
{
string[] itemParts = node.Parent.FirstNode.Text.Split(new[] { ':' }, 2);
if (itemParts.Length == 2)
{
itemCurrentVal = itemParts[1].Trim();
if (_itemShortnameToSkinName.TryGetValue(itemCurrentVal, out var skinMap) && skinMap.Count > 0)
{
comboList = new List<string>(skinMap.Keys);
skinList = skinMap;
}
}
}
}
}
using (EditValueForm editForm = new EditValueForm(propName, currentVal, valueType, comboList, _allItems))
{
if (editForm.ShowDialog() == DialogResult.OK)
{
object newVal = editForm.NewValue;
string displayVal = newVal is bool ? newVal.ToString().ToLower() : newVal.ToString();
if (_allItems.ContainsKey(displayVal))
{
newVal = _allItems[displayVal];
displayVal = _allItems[displayVal];
}
e.Node.Text = $"{propName}: {displayVal}";
if (e.Node.Tag is JsonNode node)
{
JsonNode newNode = JsonValue.Create(newVal);
node.ReplaceWith(newNode);
e.Node.Tag = newNode;
}
// Mark the file as modified.
fileModified = true;
btn_save.Enabled = true;
}
}
return (comboList, skinList, itemCurrentVal);
}

File diff suppressed because it is too large Load Diff