Add ConnectionFrameColor feature for visual environment distinction

- Added ConnectionFrameColor enum with None, Red, Yellow, Green, Blue, Purple options
- Added ConnectionFrameColor property to AbstractConnectionRecord with inheritance support
- Updated XML serialization to save/load ConnectionFrameColor
- Added language resources for ConnectionFrameColor
- Implemented visual border rendering in InterfaceControl based on ConnectionFrameColor

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-10-18 21:29:59 +00:00
parent 5b4b13828b
commit d263fb1b80
8 changed files with 111 additions and 0 deletions

View File

@@ -43,6 +43,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
element.Add(new XAttribute("Icon", connectionInfo.Icon));
element.Add(new XAttribute("Panel", connectionInfo.Panel));
element.Add(new XAttribute("TabColor", connectionInfo.TabColor));
element.Add(new XAttribute("ConnectionFrameColor", connectionInfo.ConnectionFrameColor));
element.Add(new XAttribute("Id", connectionInfo.ConstantID));
if (!Runtime.UseCredentialManager)
@@ -195,6 +196,8 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
element.Add(new XAttribute("InheritPanel", inheritance.Panel.ToString().ToLowerInvariant()));
if (inheritance.TabColor)
element.Add(new XAttribute("InheritTabColor", inheritance.TabColor.ToString().ToLowerInvariant()));
if (inheritance.ConnectionFrameColor)
element.Add(new XAttribute("InheritConnectionFrameColor", inheritance.ConnectionFrameColor.ToString().ToLowerInvariant()));
if (inheritance.Password)
element.Add(new XAttribute("InheritPassword", inheritance.Password.ToString().ToLowerInvariant()));
if (inheritance.Port)

View File

@@ -328,6 +328,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
connectionInfo.Inheritance.Icon = xmlnode.GetAttributeAsBool("InheritIcon");
connectionInfo.Inheritance.Panel = xmlnode.GetAttributeAsBool("InheritPanel");
connectionInfo.Inheritance.TabColor = xmlnode.GetAttributeAsBool("InheritTabColor");
connectionInfo.Inheritance.ConnectionFrameColor = xmlnode.GetAttributeAsBool("InheritConnectionFrameColor");
connectionInfo.Inheritance.Port = xmlnode.GetAttributeAsBool("InheritPort");
connectionInfo.Inheritance.Protocol = xmlnode.GetAttributeAsBool("InheritProtocol");
connectionInfo.Inheritance.PuttySession = xmlnode.GetAttributeAsBool("InheritPuttySession");
@@ -351,6 +352,7 @@ namespace mRemoteNG.Config.Serializers.ConnectionSerializers.Xml
connectionInfo.Icon = xmlnode.GetAttributeAsString("Icon");
connectionInfo.Panel = xmlnode.GetAttributeAsString("Panel");
connectionInfo.TabColor = xmlnode.GetAttributeAsString("TabColor");
connectionInfo.ConnectionFrameColor = xmlnode.GetAttributeAsEnum<ConnectionFrameColor>("ConnectionFrameColor");
}
else
{

View File

@@ -25,6 +25,7 @@ namespace mRemoteNG.Connection
private string _panel;
private string _color;
private string _tabColor;
private ConnectionFrameColor _connectionFrameColor;
private string _hostname;
private ExternalAddressProvider _externalAddressProvider;
@@ -181,6 +182,16 @@ namespace mRemoteNG.Connection
set => SetField(ref _tabColor, value, "TabColor");
}
[LocalizedAttributes.LocalizedCategory(nameof(Language.Display)),
LocalizedAttributes.LocalizedDisplayName(nameof(Language.ConnectionFrameColor)),
LocalizedAttributes.LocalizedDescription(nameof(Language.PropertyDescriptionConnectionFrameColor)),
TypeConverter(typeof(MiscTools.EnumTypeConverter))]
public virtual ConnectionFrameColor ConnectionFrameColor
{
get => GetPropertyValue("ConnectionFrameColor", _connectionFrameColor);
set => SetField(ref _connectionFrameColor, value, "ConnectionFrameColor");
}
#endregion
#region Connection

View File

@@ -0,0 +1,26 @@
using mRemoteNG.Tools;
using mRemoteNG.Resources.Language;
namespace mRemoteNG.Connection
{
public enum ConnectionFrameColor
{
[LocalizedAttributes.LocalizedDescription(nameof(Language.FrameColorNone))]
None = 0,
[LocalizedAttributes.LocalizedDescription(nameof(Language.FrameColorRed))]
Red = 1,
[LocalizedAttributes.LocalizedDescription(nameof(Language.FrameColorYellow))]
Yellow = 2,
[LocalizedAttributes.LocalizedDescription(nameof(Language.FrameColorGreen))]
Green = 3,
[LocalizedAttributes.LocalizedDescription(nameof(Language.FrameColorBlue))]
Blue = 4,
[LocalizedAttributes.LocalizedDescription(nameof(Language.FrameColorPurple))]
Purple = 5
}
}

View File

@@ -294,6 +294,7 @@ namespace mRemoteNG.Connection
Panel = Language.General;
Color = string.Empty;
TabColor = string.Empty;
ConnectionFrameColor = ConnectionFrameColor.None;
}
private void SetConnectionDefaults()

View File

@@ -62,6 +62,12 @@ namespace mRemoteNG.Connection
TypeConverter(typeof(MiscTools.YesNoTypeConverter))]
public bool TabColor { get; set; }
[LocalizedAttributes.LocalizedCategory(nameof(Language.Display), 2),
LocalizedAttributes.LocalizedDisplayNameInherit(nameof(Language.ConnectionFrameColor)),
LocalizedAttributes.LocalizedDescriptionInherit(nameof(Language.PropertyDescriptionConnectionFrameColor)),
TypeConverter(typeof(MiscTools.YesNoTypeConverter))]
public bool ConnectionFrameColor { get; set; }
#endregion
#region Connection

View File

@@ -32,6 +32,9 @@ namespace mRemoteNG.Connection
Size = Parent.Size;
Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
InitializeComponent();
// Enable custom painting for border
this.Paint += InterfaceControl_Paint;
}
catch (Exception ex)
{
@@ -41,6 +44,41 @@ namespace mRemoteNG.Connection
}
}
private void InterfaceControl_Paint(object sender, PaintEventArgs e)
{
// Draw colored border based on ConnectionFrameColor property
if (Info?.ConnectionFrameColor != null && Info.ConnectionFrameColor != ConnectionFrameColor.None)
{
Color frameColor = GetFrameColor(Info.ConnectionFrameColor);
int borderWidth = 4; // 4 pixel border for visibility
using (Pen pen = new Pen(frameColor, borderWidth))
{
// Draw border inside the control bounds
Rectangle rect = new Rectangle(
borderWidth / 2,
borderWidth / 2,
this.Width - borderWidth,
this.Height - borderWidth
);
e.Graphics.DrawRectangle(pen, rect);
}
}
}
private Color GetFrameColor(ConnectionFrameColor frameColor)
{
return frameColor switch
{
ConnectionFrameColor.Red => Color.FromArgb(220, 53, 69), // Bootstrap danger red
ConnectionFrameColor.Yellow => Color.FromArgb(255, 193, 7), // Warning yellow
ConnectionFrameColor.Green => Color.FromArgb(40, 167, 69), // Success green
ConnectionFrameColor.Blue => Color.FromArgb(0, 123, 255), // Primary blue
ConnectionFrameColor.Purple => Color.FromArgb(111, 66, 193), // Purple
_ => Color.Transparent
};
}
public static InterfaceControl FindInterfaceControl(DockPanel DockPnl)
{
// instead of repeating the code, call the routine using ConnectionTab if called by DockPanel

View File

@@ -2517,4 +2517,28 @@ Nightly Channel includes Alphas, Betas &amp; Release Candidates.</value>
<value>Community</value>
<comment>Reddit</comment>
</data>
<data name="ConnectionFrameColor" xml:space="preserve">
<value>Connection Frame Color</value>
</data>
<data name="PropertyDescriptionConnectionFrameColor" xml:space="preserve">
<value>Sets a colored border around the connection panel to visually distinguish between different environments (e.g., production, test, development).</value>
</data>
<data name="FrameColorNone" xml:space="preserve">
<value>None</value>
</data>
<data name="FrameColorRed" xml:space="preserve">
<value>Red (Production)</value>
</data>
<data name="FrameColorYellow" xml:space="preserve">
<value>Yellow (Staging/UAT)</value>
</data>
<data name="FrameColorGreen" xml:space="preserve">
<value>Green (Test)</value>
</data>
<data name="FrameColorBlue" xml:space="preserve">
<value>Blue (Development)</value>
</data>
<data name="FrameColorPurple" xml:space="preserve">
<value>Purple (Custom)</value>
</data>
</root>