From 5d623d80eb7eae3b28d7a2a9b68898281913f199 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 20:10:54 +0000 Subject: [PATCH 1/2] Initial plan From 347546ee0e580e843f4db520f72101900f1bb4d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 20:15:32 +0000 Subject: [PATCH 2/2] Add TabColorConverter to fix Color to String conversion issue Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com> --- .../Connection/AbstractConnectionRecord.cs | 2 +- mRemoteNG/Tools/MiscTools.cs | 126 +++++++++++++++ .../Tools/TabColorConverterTests.cs | 148 ++++++++++++++++++ 3 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 mRemoteNGTests/Tools/TabColorConverterTests.cs diff --git a/mRemoteNG/Connection/AbstractConnectionRecord.cs b/mRemoteNG/Connection/AbstractConnectionRecord.cs index c16b0ec6..90e1a3db 100644 --- a/mRemoteNG/Connection/AbstractConnectionRecord.cs +++ b/mRemoteNG/Connection/AbstractConnectionRecord.cs @@ -158,7 +158,7 @@ namespace mRemoteNG.Connection [LocalizedAttributes.LocalizedCategory(nameof(Language.Display)), LocalizedAttributes.LocalizedDisplayName(nameof(Language.TabColor)), LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionTabColor)), - TypeConverter(typeof(ColorConverter))] + TypeConverter(typeof(MiscTools.TabColorConverter))] public virtual string TabColor { get => GetPropertyValue("TabColor", _tabColor); diff --git a/mRemoteNG/Tools/MiscTools.cs b/mRemoteNG/Tools/MiscTools.cs index d35af82a..c1579691 100644 --- a/mRemoteNG/Tools/MiscTools.cs +++ b/mRemoteNG/Tools/MiscTools.cs @@ -268,5 +268,131 @@ namespace mRemoteNG.Tools return svc; } } + + public class TabColorConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + { + return sourceType == typeof(string) || sourceType == typeof(Color) || base.CanConvertFrom(context, sourceType); + } + + public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) + { + return destinationType == typeof(string) || destinationType == typeof(Color) || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object? value) + { + if (value == null || (value is string str && string.IsNullOrWhiteSpace(str))) + { + return string.Empty; + } + + if (value is string stringValue) + { + return stringValue; + } + + if (value is Color colorValue) + { + // Convert Color to string representation + // Use named color if it's a known color, otherwise use hex format + if (colorValue.IsNamedColor) + { + return colorValue.Name; + } + else + { + // Return hex format without alpha if fully opaque, otherwise include alpha + if (colorValue.A == 255) + { + return $"#{colorValue.R:X2}{colorValue.G:X2}{colorValue.B:X2}"; + } + else + { + return $"#{colorValue.A:X2}{colorValue.R:X2}{colorValue.G:X2}{colorValue.B:X2}"; + } + } + } + + return base.ConvertFrom(context, culture, value); + } + + public override object ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (destinationType == typeof(string)) + { + if (value == null || (value is string str && string.IsNullOrWhiteSpace(str))) + { + return string.Empty; + } + return value.ToString() ?? string.Empty; + } + + if (destinationType == typeof(Color)) + { + if (value == null || (value is string str && string.IsNullOrWhiteSpace(str))) + { + return Color.Empty; + } + + if (value is string stringValue) + { + try + { + ColorConverter converter = new ColorConverter(); + return converter.ConvertFromString(stringValue) ?? Color.Empty; + } + catch + { + return Color.Empty; + } + } + } + + return base.ConvertTo(context, culture, value, destinationType) ?? throw new InvalidOperationException("Base conversion returned null."); + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) + { + return true; + } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext? context) + { + // Provide a list of common colors for the dropdown + Color[] colors = + [ + Color.Red, + Color.Orange, + Color.Yellow, + Color.Green, + Color.Blue, + Color.Purple, + Color.Pink, + Color.Brown, + Color.Black, + Color.White, + Color.Gray, + Color.LightGray, + Color.DarkGray, + Color.Cyan, + Color.Magenta, + Color.Lime, + Color.Navy, + Color.Teal, + Color.Maroon, + Color.Olive + ]; + + return new StandardValuesCollection(colors); + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) + { + // Return false to allow custom values (hex codes or other color names) + return false; + } + } } } \ No newline at end of file diff --git a/mRemoteNGTests/Tools/TabColorConverterTests.cs b/mRemoteNGTests/Tools/TabColorConverterTests.cs new file mode 100644 index 00000000..36c089d5 --- /dev/null +++ b/mRemoteNGTests/Tools/TabColorConverterTests.cs @@ -0,0 +1,148 @@ +using System.Drawing; +using mRemoteNG.Tools; +using NUnit.Framework; + +namespace mRemoteNGTests.Tools +{ + public class TabColorConverterTests + { + private MiscTools.TabColorConverter _converter; + + [SetUp] + public void Setup() + { + _converter = new MiscTools.TabColorConverter(); + } + + [TestCase(typeof(string), true)] + [TestCase(typeof(Color), true)] + public void CanConvertFrom(Type typeToConvertFrom, bool expectedOutcome) + { + var actualOutcome = _converter.CanConvertFrom(typeToConvertFrom); + Assert.That(actualOutcome, Is.EqualTo(expectedOutcome)); + } + + [TestCase(typeof(string), true)] + [TestCase(typeof(Color), true)] + public void CanConvertTo(Type typeToConvertTo, bool expectedOutcome) + { + var actualOutcome = _converter.CanConvertTo(typeToConvertTo); + Assert.That(actualOutcome, Is.EqualTo(expectedOutcome)); + } + + [Test] + public void ConvertFromColorToStringNamedColor() + { + var color = Color.Red; + var result = _converter.ConvertFrom(color); + Assert.That(result, Is.EqualTo("Red")); + } + + [Test] + public void ConvertFromColorToStringCustomColor() + { + var color = Color.FromArgb(255, 128, 64, 32); + var result = _converter.ConvertFrom(color); + Assert.That(result, Is.EqualTo("#80401F")); + } + + [Test] + public void ConvertFromColorToStringCustomColorWithAlpha() + { + var color = Color.FromArgb(128, 255, 0, 0); + var result = _converter.ConvertFrom(color); + Assert.That(result, Is.EqualTo("#80FF0000")); + } + + [Test] + public void ConvertFromStringReturnsString() + { + var colorString = "Blue"; + var result = _converter.ConvertFrom(colorString); + Assert.That(result, Is.EqualTo("Blue")); + } + + [Test] + public void ConvertFromHexStringReturnsString() + { + var colorString = "#FF0000"; + var result = _converter.ConvertFrom(colorString); + Assert.That(result, Is.EqualTo("#FF0000")); + } + + [Test] + public void ConvertFromNullReturnsEmptyString() + { + var result = _converter.ConvertFrom(null); + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void ConvertFromEmptyStringReturnsEmptyString() + { + var result = _converter.ConvertFrom(""); + Assert.That(result, Is.EqualTo(string.Empty)); + } + + [Test] + public void ConvertToStringFromString() + { + var colorString = "Green"; + var result = _converter.ConvertTo(colorString, typeof(string)); + Assert.That(result, Is.EqualTo("Green")); + } + + [Test] + public void ConvertToColorFromNamedString() + { + var colorString = "Red"; + var result = _converter.ConvertTo(colorString, typeof(Color)); + Assert.That(result, Is.EqualTo(Color.Red)); + } + + [Test] + public void ConvertToColorFromHexString() + { + var colorString = "#FF0000"; + var result = _converter.ConvertTo(colorString, typeof(Color)); + var expectedColor = Color.FromArgb(255, 255, 0, 0); + Assert.That(result, Is.EqualTo(expectedColor)); + } + + [Test] + public void ConvertToColorFromEmptyStringReturnsEmpty() + { + var result = _converter.ConvertTo("", typeof(Color)); + Assert.That(result, Is.EqualTo(Color.Empty)); + } + + [Test] + public void ConvertToColorFromNullReturnsEmpty() + { + var result = _converter.ConvertTo(null, typeof(Color)); + Assert.That(result, Is.EqualTo(Color.Empty)); + } + + [Test] + public void GetStandardValuesSupportedReturnsTrue() + { + var result = _converter.GetStandardValuesSupported(null); + Assert.That(result, Is.True); + } + + [Test] + public void GetStandardValuesReturnsColorList() + { + var result = _converter.GetStandardValues(null); + Assert.That(result, Is.Not.Null); + Assert.That(result.Count, Is.GreaterThan(0)); + } + + [Test] + public void GetStandardValuesExclusiveReturnsFalse() + { + var result = _converter.GetStandardValuesExclusive(null); + Assert.That(result, Is.False); + } + } +}