Add project files.

This commit is contained in:
Jose Conde
2025-09-04 10:14:30 +02:00
parent a7a404148c
commit 94e6ef651e
54 changed files with 3134 additions and 0 deletions

View File

@@ -0,0 +1,477 @@
using Microsoft.VisualBasic.FileIO;
using ModVersionChecker.data.model;
using ModVersionChecker.managers.interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
namespace ModVersionChecker.forms
{
public class AppDetailsForm : Form
{
private readonly IConfigManager _configManager;
private readonly IAppsManager _appsManager;
private readonly ISourcesDefManager _sourcesDefManager;
private readonly ICheckerTypesDefManager _checkerTypesDefManager;
private readonly IFlightSimsManager _flightSimsManager;
private readonly GlobalConfig _globalConfig;
private int _currentRow;
//private string? _appId;
private bool _isEditable;
//private List<AppConfig> _apps;
private List<SourceDef> _sourcesDef;
private List<CheckerTypeDef> _checkerTypesDef;
private TextBox _nameField, _downloadUrlField;
private Label _nameLabel, _msfsVersionsLabel, _sourceLabel, _paramsSubtitle, _downloadUrlLabel;
private ComboBox _sourceField;
private Button _saveButton, _closeButton;
private TableLayoutPanel _mainLayout, _paramsPanel, _fsFieldsPanel;
private FlowLayoutPanel _buttonsPanel, _fsPanel;
private readonly Dictionary<string, TextBox> _paramFields = new Dictionary<string, TextBox>();
private readonly Dictionary<string, Dictionary<string, TextBox>> _fsFields = new Dictionary<string, Dictionary<string, TextBox>>();
private List<string> _selectedFs = new List<string>();
private List<CheckBox> _fsCheckBoxes = new List<CheckBox>();
private AppConfig? _currentApp;
private List<FsModPathConfig> _flightSims;
public event EventHandler<string> OnAppChanged;
public AppDetailsForm(
IConfigManager configManager,
IAppsManager appsManager,
ISourcesDefManager sourcesDefManager,
ICheckerTypesDefManager checkerTypesDefManager,
IFlightSimsManager flightSimsManager
)
{
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
_appsManager = appsManager ?? throw new ArgumentNullException(nameof(appsManager));
_sourcesDefManager = sourcesDefManager ?? throw new ArgumentNullException(nameof(sourcesDefManager));
_checkerTypesDefManager = checkerTypesDefManager ?? throw new ArgumentNullException(nameof(checkerTypesDefManager));
_flightSimsManager = flightSimsManager ?? throw new ArgumentNullException(nameof(flightSimsManager));
_flightSims = _flightSimsManager.Load() ?? new List<FsModPathConfig>();
_globalConfig = _configManager.Load() ?? new GlobalConfig();
_sourcesDef = _sourcesDefManager.List() ?? new List<SourceDef>();
_checkerTypesDef = _checkerTypesDefManager.Load() ?? new List<CheckerTypeDef>();
_selectedFs = _flightSims.Select(sim => sim.ShortName).ToList();
_mainLayout = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 2,
RowCount = 14,
ColumnStyles = { new ColumnStyle(SizeType.Absolute, 150), new ColumnStyle(SizeType.Percent, 100) }
};
// App Name
_nameLabel = new Label { Text = "Name:" };
_nameField = new TextBox { Text = "", Enabled = _isEditable, Width = 300 };
// FS Versions
_msfsVersionsLabel = new Label { Text = "FS:" };
_fsPanel = new FlowLayoutPanel
{
FlowDirection = FlowDirection.LeftToRight,
AutoSize = true,
Dock = DockStyle.Fill
};
//_msfs2020CheckBox = new CheckBox { Text = "MSFS 2020", Enabled = _isEditable };
//_msfs2024CheckBox = new CheckBox { Text = "MSFS 2024", Enabled = _isEditable };
// Source
_sourceLabel = new Label { Text = "Source:" };
_sourceField = new ComboBox { Enabled = _isEditable, Width = 300, DropDownStyle = ComboBoxStyle.DropDownList };
_sourceField.Items.AddRange(_sourcesDef.Select(sd => sd.Id).ToArray());
_sourceField.SelectedIndexChanged += OnSourceFieldIndexChanged;
// Parameters
_paramsSubtitle = new Label { Text = "SourceParameters:", Font = new System.Drawing.Font(Font, System.Drawing.FontStyle.Bold) };
_paramsPanel = new TableLayoutPanel
{
AutoSize = true,
BackColor = Color.White,
Dock = DockStyle.Fill,
ColumnCount = 2,
RowCount = 2,
ColumnStyles = { new ColumnStyle(SizeType.Absolute, 150), new ColumnStyle(SizeType.Percent, 100) }
};
// Fs Fields Panel
_fsFieldsPanel = new TableLayoutPanel
{
AutoSize = true,
BackColor = Color.White,
Dock = DockStyle.Fill,
ColumnCount = 2,
RowCount = 2,
ColumnStyles = { new ColumnStyle(SizeType.Absolute, 150), new ColumnStyle(SizeType.Percent, 100) }
};
// App Name
_downloadUrlLabel = new Label { Text = "Download Url:" };
_downloadUrlField = new TextBox { Text = "", Enabled = _isEditable, Width = 300 };
_buttonsPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, AutoSize = true, Dock = DockStyle.Fill };
_saveButton = new Button { Text = "Save", Width = 100 };
_closeButton = new Button { Text = "Close", Width = 100 };
_saveButton.Click += OnSaveButtonClicked;
_closeButton.Click += (s, e) => Close();
Controls.Add(_mainLayout);
Size = new System.Drawing.Size(500, 500);
StartPosition = FormStartPosition.CenterParent;
InitializeForm();
}
public void SetApp(AppConfig? app, bool update = true)
{
_currentApp = app;
_selectedFs = _currentApp?.MsfsVersions ?? new List<string>();
if (update)
{
UpdateForm();
}
}
public void SetEditable(bool isEditable, bool update = true)
{
_isEditable = isEditable;
if (update)
{
UpdateForm();
}
}
public void UpdateForm()
{
Text = _currentApp == null ? "Add App" : (_isEditable ? "Edit App" : "App Details");
_nameField.Text = _currentApp != null ? _currentApp.Name : "";
_downloadUrlField.Enabled = _nameField.Enabled = _sourceField.Enabled = _isEditable;
_downloadUrlField.Text = _currentApp != null ? _currentApp.DownloadUrl : "";
_flightSims.ForEach(fs =>
{
if (_currentApp != null && _currentApp.MsfsVersions.Contains(fs.ShortName))
{
if (!_selectedFs.Contains(fs.ShortName))
{
_selectedFs.Add(fs.ShortName);
}
}
});
for (int i = 0; i < _fsCheckBoxes.Count; i++)
{
var fsKey = _flightSims.FirstOrDefault(f => f.ShortName == _fsCheckBoxes[i].Text)?.ShortName;
if (fsKey != null)
{
_fsCheckBoxes[i].Checked = _currentApp != null && _currentApp.MsfsVersions.Contains(fsKey);
}
}
_sourceField.SelectedIndex = _sourceField.Items.IndexOf(_currentApp != null ? _currentApp.Source : "");
UpdateFsFields();
UpdateParamFields();
}
private bool isFsSelected(FsModPathConfig fs)
{
return _selectedFs.Contains(fs.ShortName);
}
private void UpdateFsFields()
{
_fsFields.Clear();
_fsFieldsPanel.Controls.Clear();
foreach (var fs in _flightSims)
{
if (fs == null || !isFsSelected(fs))
{
continue;
}
var fsKey = fs.ShortName;
var fieldsDict = new Dictionary<string, TextBox>();
_fsFields[fsKey] = fieldsDict;
int currentRow = 0;
Label horizontalSeparator = new Label
{
Height = 50,
Padding = new Padding(10, 0, 0, 0),
BackColor = Color.GhostWhite, // Line-like separator
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft,
Text = fsKey, // Optional: Add text to the separator
ForeColor = Color.FromArgb(50, 50, 50) // Text color contrasts with background
};
_fsFieldsPanel.Controls.Add(horizontalSeparator, 0, currentRow);
_fsFieldsPanel.SetColumnSpan(horizontalSeparator, 2);
currentRow++;
foreach (var field in fs.Fields)
{
Control control;
var value = GetFsFieldValue(fsKey, field.Name);
var label = new Label { Text = $"{field.Label} ({(field.Required ? "Required" : "Optional")}):", Width = 100, AutoSize = true };
var textBox = new TextBox
{
Width = 300,
Enabled = _isEditable,
Text = value
};
switch (field.Control.ToLower())
{
case "directory":
textBox.ReadOnly = true;
control = new TableLayoutPanel
{
AutoSize = true,
Dock = DockStyle.Fill,
ColumnCount = 2,
RowCount = 1,
ColumnStyles = { new ColumnStyle(SizeType.Percent, 80), new ColumnStyle(SizeType.Percent, 20) }
};
(control as TableLayoutPanel).Controls.Add(textBox, 0, 0);
var browseButton = new Button { Text = "Browse", Width = 80, Enabled = _isEditable };
browseButton.Click += (s, e) =>
{
using (var folderDialog = new FolderBrowserDialog())
{
folderDialog.Description = $"Select directory for {field.Label}";
folderDialog.SelectedPath = textBox.Text == "" ? Path.Combine(fs.Path) : textBox.Text;
if (folderDialog.ShowDialog() == DialogResult.OK)
{
string selectedDirectory = folderDialog.SelectedPath;
string folderName = Path.GetFileName(selectedDirectory);
textBox.Text = folderName;
}
}
};
(control as TableLayoutPanel).Controls.Add(browseButton, 1, 0);
break;
default:
control = textBox;
break;
}
fieldsDict[field.Name] = textBox;
_fsFieldsPanel.Controls.Add(label, 0, currentRow);
_fsFieldsPanel.Controls.Add(control, 1, currentRow);
currentRow++;
}
}
}
private string GetFsFieldValue(string fsKey, string fieldName)
{
if (_currentApp == null) return "";
var fsFields = _currentApp.FsFields.ContainsKey(fsKey) ? _currentApp.FsFields[fsKey] : new Dictionary<string, string>();
if (fsFields.ContainsKey(fieldName))
{
return fsFields[fieldName];
}
return "";
}
private void UpdateParamFields()
{
if (_sourceField?.SelectedItem == null) return;
var selectedSource = _sourcesDef.FirstOrDefault(sd => sd.Id == _sourceField.SelectedItem.ToString());
if (selectedSource == null) return;
var checkerType = _checkerTypesDef.FirstOrDefault(ct => ct.Name == selectedSource.Type);
if (checkerType == null) return;
_paramFields.Clear();
_paramsPanel.Controls.Clear();
int currentRow = 0;
foreach (var paramDef in checkerType.Params)
{
var label = new Label { Text = $"{paramDef.Label} ({(paramDef.Required ? "Required" : "Optional")}):", Width = 100, AutoSize = true };
var textBox = new TextBox
{
Width = 300,
Enabled = _isEditable,
Text = GetParamValue(paramDef.Name, selectedSource)
};
_paramFields[paramDef.Name] = textBox;
_paramsPanel.Controls.Add(label, 0, currentRow);
_paramsPanel.Controls.Add(textBox, 1, currentRow);
currentRow++;
}
}
private string GetParamValue(string paramName, SourceDef source)
{
var valueFromSource = source.Defaults != null && source.Defaults.ContainsKey(paramName) ? source.Defaults[paramName] : "";
if (_currentApp == null || _currentApp.Params == null || !_currentApp.Params.ContainsKey(paramName))
return valueFromSource;
return _currentApp.Params[paramName];
}
private void InitializeForm()
{
_currentRow = 0;
_mainLayout.Controls.Add(_nameLabel, 0, _currentRow);
_mainLayout.Controls.Add(_nameField, 1, _currentRow++);
_mainLayout.Controls.Add(_msfsVersionsLabel, 0, _currentRow++);
_mainLayout.Controls.Add(_fsPanel, 1, _currentRow);
_mainLayout.SetColumnSpan(_fsPanel, 2);
_currentRow++;
_mainLayout.Controls.Add(_fsFieldsPanel, 0, _currentRow);
_mainLayout.SetColumnSpan(_fsFieldsPanel, 2);
_currentRow++;
_mainLayout.Controls.Add(_sourceLabel, 0, _currentRow);
_mainLayout.Controls.Add(_sourceField, 1, _currentRow++);
_mainLayout.Controls.Add(_paramsSubtitle, 0, _currentRow);
_mainLayout.SetColumnSpan(_paramsSubtitle, 2);
_currentRow++;
_mainLayout.Controls.Add(_paramsPanel, 0, _currentRow);
_mainLayout.SetColumnSpan(_paramsPanel, 2);
_currentRow++;
_mainLayout.Controls.Add(_downloadUrlLabel, 0, _currentRow);
_mainLayout.Controls.Add(_downloadUrlField, 1, _currentRow++);
_currentRow++;
_mainLayout.Controls.Add(_buttonsPanel, 0, _currentRow++);
AddFsCheckboxes();
AddButtons();
// UpdateForm();
}
private void AddFsCheckboxes()
{
foreach (var fs in _flightSims)
{
var checkBox = new CheckBox
{
Text = fs.ShortName,
Checked = _currentApp != null && _currentApp.MsfsVersions.Contains(fs.ShortName),
};
checkBox.CheckedChanged += (s, e) =>
{
if (checkBox.Checked)
{
if (!_selectedFs.Contains(fs.ShortName))
{
_selectedFs.Add(fs.ShortName);
}
}
else
{
_selectedFs.Remove(fs.ShortName);
}
UpdateFsFields();
};
_fsPanel.Controls.Add(checkBox);
_fsCheckBoxes.Add(checkBox);
}
}
private void OnSourceFieldIndexChanged(object? sender, EventArgs e)
{
if (_isEditable && _sourceField.SelectedItem != null)
{
UpdateParamFields();
}
}
private void AddButtons()
{
_buttonsPanel.Controls.Clear();
_buttonsPanel.Controls.Add(_saveButton);
_buttonsPanel.Controls.Add(_closeButton);
}
private void OnSaveButtonClicked(object? sender, EventArgs e)
{
try {
var paramsDict = _paramFields.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Text.Trim());
var fsFieldsDict = _fsFields.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.ToDictionary(fkvp => fkvp.Key, fkvp => fkvp.Value.Text.Trim())
);
var requiredParams = _checkerTypesDef
.First(ct => ct.Name == _sourcesDef.FirstOrDefault(sd => sd.Id == _sourceField.SelectedItem?.ToString())?.Type)
.Params.Where(p => p.Required)
.Select(p => p.Name);
if (requiredParams.Any(rp => string.IsNullOrWhiteSpace(paramsDict[rp])))
{
throw new Exception("All required parameters must be filled.");
}
var msfsVersions = _selectedFs;
var isNewApp = (_currentApp == null || string.IsNullOrEmpty(_currentApp.Id));
var app = new AppConfig
{
Id = isNewApp ? GetUuid() : _currentApp.Id,
Name = _nameField.Text.Trim(),
MsfsVersions = msfsVersions,
Source = _sourceField.SelectedItem?.ToString() ?? "",
Params = paramsDict,
FsFields = fsFieldsDict,
DownloadUrl = _downloadUrlField.Text.Trim(),
CurrentVersion = _currentApp?.CurrentVersion ?? "",
LatestVersion = _currentApp?.LatestVersion ?? "",
Status = _currentApp?.Status ?? AppStatus.None
};
if (isNewApp)
{
_appsManager.Insert(app);
} else
{
_appsManager.Update(app);
}
_currentApp = app;
OnAppChanged?.Invoke(this, "App saved");
Close();
}
catch (Exception ex)
{
MessageBox.Show($"Error: {ex.Message}", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private string GetUuid()
{
Guid uuid = Guid.NewGuid();
return uuid.ToString();
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,68 @@
using Microsoft.Extensions.DependencyInjection;
using ModVersionChecker.data.model;
using ModVersionChecker.managers.interfaces;
namespace ModVersionChecker.forms
{
public class FormFactory : IFormFactory
{
private readonly IServiceProvider _serviceProvider;
public FormFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
public AppDetailsForm CreateAppDetailsForm(AppConfig? app, bool isEditable, EventHandler<string>? onAppChanged)
{
var configManager = _serviceProvider.GetRequiredService<IConfigManager>();
var appsManager = _serviceProvider.GetRequiredService<IAppsManager>();
var sourcesDefManager = _serviceProvider.GetRequiredService<ISourcesDefManager>();
var checkerTypesDefManager = _serviceProvider.GetRequiredService<ICheckerTypesDefManager>();
var flightSimsManager = _serviceProvider.GetRequiredService<IFlightSimsManager>();
var form = new AppDetailsForm(configManager, appsManager, sourcesDefManager, checkerTypesDefManager, flightSimsManager);
form.SetApp(app, false);
form.SetEditable(isEditable);
if (onAppChanged != null)
{
form.OnAppChanged += onAppChanged;
}
return form;
}
public GlobalConfigForm CreateGlobalConfigForm()
{
var configManager = _serviceProvider.GetRequiredService<IConfigManager>();
var form = new GlobalConfigForm(configManager);
return form;
}
public SourcesConfigForm CreateSourcesConfigForm(EventHandler<string>? onSourcesChanged)
{
var sourcesDefManager = _serviceProvider.GetRequiredService<ISourcesDefManager>();
var formFactory = _serviceProvider.GetRequiredService<IFormFactory>();
var form = new SourcesConfigForm(formFactory, sourcesDefManager);
if (onSourcesChanged != null)
{
form.OnSourcesChanged += onSourcesChanged;
}
return form;
}
public SourceDetailForm CreateSourceDetailForm(SourceDef? sourceDef, EventHandler<string>? onSourceChanged)
{
var sourcesDefManager = _serviceProvider.GetRequiredService<ISourcesDefManager>();
var checkerTypesDefManager = _serviceProvider.GetRequiredService<ICheckerTypesDefManager>();
var formFactory = _serviceProvider.GetRequiredService<IFormFactory>();
var form = new SourceDetailForm(formFactory, sourcesDefManager);
form.SourceDef = sourceDef;
if (onSourceChanged != null)
{
form.UpdateFields();
form.OnSourceChanged += onSourceChanged;
}
return form;
}
}
}

View File

@@ -0,0 +1,122 @@
using ModVersionChecker.data.model;
using ModVersionChecker.managers.interfaces;
namespace ModVersionChecker.forms
{
public class GlobalConfigForm : Form
{
private IConfigManager _configManager;
private GlobalConfig _config;
private Label _millislabel, _checkStartupLabel;
private TrackBar _millisField;
private CheckBox _checkStartupField;
private Button _saveButton, _cancelButton;
private TableLayoutPanel _mainLayout, _configsPanel;
private FlowLayoutPanel _buttonPanel;
public GlobalConfigForm(IConfigManager configManager)
{
_configManager = configManager;
_config = _configManager.GetConfig();
InitializeComponent();
}
private void InitializeComponent()
{
SuspendLayout();
ClientSize = new System.Drawing.Size(600, 250);
Name = "GlobalConfigForm";
Text = "Global Configuration";
StartPosition = FormStartPosition.CenterParent;
Padding = new Padding(10, 20, 10, 20 );
_mainLayout = GetMainLayout();
_configsPanel = GetConfigsPanel();
_buttonPanel = GetButtonsPanel();
_mainLayout.Controls.Add(_configsPanel, 0, 0);
_mainLayout.Controls.Add(_buttonPanel, 0, 1);
Controls.Add(_mainLayout);
ResumeLayout(false);
}
private FlowLayoutPanel GetButtonsPanel()
{
var buttonsPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, AutoSize = true, Dock = DockStyle.Fill };
_saveButton = new Button { Text = "Save", AutoSize = true };
_saveButton.Click += (sender, e) =>
{
_config.IntervalMinutes = _millisField.Value;
_config.CheckOnStartup = _checkStartupField.Checked;
_configManager.Save(_config);
DialogResult = DialogResult.OK;
Close();
};
_cancelButton = new Button { Text = "Cancel", AutoSize = true };
_cancelButton.Click += (sender, e) =>
{
DialogResult = DialogResult.Cancel;
Close();
};
buttonsPanel.Controls.Add(_saveButton);
buttonsPanel.Controls.Add(_cancelButton);
return buttonsPanel;
}
private TableLayoutPanel GetConfigsPanel()
{
// Initialize the configurations panel
var configsPanel = new TableLayoutPanel
{
AutoSize = true,
Dock = DockStyle.Fill,
ColumnCount = 2,
RowCount = 2,
ColumnStyles = { new ColumnStyle(SizeType.Absolute, 150), new ColumnStyle(SizeType.Percent, 100) }
};
_millislabel = new Label { Text = "Millis", Width = 150};
_millisField = new TrackBar { Minimum = 0, Maximum = 120, Value= _config.IntervalMinutes, Width = 300, TickStyle = TickStyle.None };
FlowLayoutPanel millisPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.LeftToRight, AutoSize = true };
Label millisValue = new Label { Text = _millisField.Value.ToString() + " minutes", AutoSize = true, Padding = new Padding(10, 10, 0, 0) };
millisPanel.Controls.Add(_millisField);
millisPanel.Controls.Add(millisValue);
_millisField.Scroll += (sender, e) => { millisValue.Text = _millisField.Value.ToString() + " minutes"; };
_checkStartupLabel = new Label { Text = "Check on Startup:", Width = 150 };
_checkStartupField = new CheckBox { Checked = _config.CheckOnStartup };
configsPanel.Controls.Add(_millislabel, 0, 0);
configsPanel.Controls.Add(millisPanel, 1, 0);
configsPanel.Controls.Add(_checkStartupLabel, 0, 1);
configsPanel.Controls.Add(_checkStartupField, 1, 1);
return configsPanel;
}
private TableLayoutPanel GetMainLayout()
{
// Initialize the main layout panel
var mainLayout = new TableLayoutPanel
{
Dock = DockStyle.Fill,
RowCount = 2,
ColumnCount = 1
};
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 150)); // Paths panel height
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 50)); // Button panel height
Controls.Add(mainLayout);
return mainLayout;
}
// Add methods and properties for global configuration management here
}
}

View File

@@ -0,0 +1,14 @@
using ModVersionChecker.data.model;
namespace ModVersionChecker.forms
{
public interface IFormFactory
{
AppDetailsForm CreateAppDetailsForm(AppConfig? app, bool isEditable, EventHandler<string>? onAppChanged);
GlobalConfigForm CreateGlobalConfigForm();
SourcesConfigForm CreateSourcesConfigForm(EventHandler<string>? onSourcesChanged);
SourceDetailForm CreateSourceDetailForm(SourceDef? sourceDef, EventHandler<string>? onSourceChanged);
}
}

View File

@@ -0,0 +1,310 @@
using ModVersionChecker.data.model;
using ModVersionChecker.managers.interfaces;
using ModVersionChecker.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Intrinsics.Arm;
using System.Windows.Forms;
namespace ModVersionChecker.forms
{
public class MainForm : Form
{
private readonly IConfigManager _configManager;
private readonly IAppsManager _appsManager;
private readonly IFormFactory _formFactory;
private readonly IAppStatusManager _appStatusManager;
private readonly IFlightSimsManager _fsManager;
private readonly TableLayoutPanel _mainLayout;
private readonly GlobalConfig _globalConfig;
private List<AppConfig> _apps = new List<AppConfig>();
private ListView _listView;
private ImageList _statusImageList = new ImageList();
public event EventHandler<EventArgs> OnConfigChanged;
public event EventHandler<string> OnRecheck;
private EventHandler<string> onAppChangedHandler;
private MenuStrip _menuStrip;
private List<FsModPathConfig> _fsMods;
private readonly Dictionary<string, TextBox> _fsModPathTextBoxes = new Dictionary<string, TextBox>();
public MainForm(IConfigManager configManager, IAppsManager appsManager, IFormFactory formFactory, IAppStatusManager appStatusManager, IFlightSimsManager fsManager)
{
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
_appsManager = appsManager ?? throw new ArgumentNullException(nameof(appsManager));
_formFactory = formFactory ?? throw new ArgumentNullException(nameof(formFactory));
_appStatusManager = appStatusManager ?? throw new ArgumentNullException(nameof(appStatusManager));
_fsManager = fsManager ?? throw new ArgumentNullException(nameof(fsManager));
_fsMods = _fsManager.Load();
_statusImageList.Images.Add("none", new Icon("Resources/ok-icon.ico"));
_statusImageList.Images.Add("update", new Icon("Resources/up-icon.ico"));
_statusImageList.Images.Add("error", new Icon("Resources/error-icon.ico"));
Text = "Update Checker Configuration";
Size = new Size(600, 800);
StartPosition = FormStartPosition.CenterScreen;
_globalConfig = configManager.Load() ?? new GlobalConfig();
_mainLayout = GetMainLayout();
_mainLayout.Controls.Add(GetPathsPanel(), 0, 0);
_listView = GetListView();
_listView.SmallImageList = _statusImageList;
_mainLayout.Controls.Add(_listView , 0, 1);
_mainLayout.Controls.Add(GetButtonsPanel(), 0, 2);
onAppChangedHandler = (s2, e) =>
{
UpdateListView();
OnConfigChanged?.Invoke(this, EventArgs.Empty);
};
InitializeMenu();
}
private TableLayoutPanel GetMainLayout()
{
// Initialize the main layout panel
var mainLayout = new TableLayoutPanel
{
Dock = DockStyle.Fill,
RowCount = 3,
ColumnCount = 1
};
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 150)); // Paths panel height
mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 70)); // ListView takes remaining space
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 50)); // Button panel height
Controls.Add(mainLayout);
return mainLayout;
}
private FlowLayoutPanel GetPathsPanel()
{
var pathPanel = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.TopDown };
foreach (var fsMod in _fsMods)
{
var singlePathPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.LeftToRight, AutoSize = true };
singlePathPanel.Controls.Add(new Label { Text = $"{fsMod.Name} Path:", Width = 100 });
var pathField = new TextBox { Text = fsMod.Path ?? "", Width = 300 };
singlePathPanel.Controls.Add(pathField);
_fsModPathTextBoxes.Add(fsMod.ShortName, pathField);
pathPanel.Controls.Add(singlePathPanel);
}
//var pathPanel2024 = new FlowLayoutPanel { FlowDirection = FlowDirection.LeftToRight, AutoSize = true };
//pathPanel2024.Controls.Add(new Label { Text = "MSFS 2024 Path:", Width = 100 });
//var msfs2024PathField = new TextBox { Text = _globalConfig.FsModPaths.ContainsKey("msfs2024") ? _globalConfig.FsModPaths["msfs2024"].Path : "", Width = 300 };
//pathPanel2024.Controls.Add(msfs2024PathField);
//pathPanel.Controls.Add(pathPanel2024);
var savePathsButton = new Button { Text = "Save Paths" };
savePathsButton.Click += (s, e) =>
{
foreach (var fsMod in _fsMods)
{
fsMod.Path = _fsModPathTextBoxes[fsMod.ShortName].Text;
_fsManager.Save(fsMod);
}
_fsMods = _fsManager.Load();
};
pathPanel.Controls.Add(savePathsButton);
return pathPanel;
}
private ListView GetListView()
{
var listView = new ListView
{
Dock = DockStyle.Fill,
View = View.Details,
FullRowSelect = true,
MultiSelect = false,
Visible = true,
Sorting = SortOrder.Ascending
};
listView.Columns.Add("Name", 150);
listView.Columns.Add("MSFS Versions", 100);
listView.Columns.Add("Current", 80);
listView.Columns.Add("Latest", 80);
listView.Columns.Add("Last Checked", 150);
listView.DoubleClick += (s, e) =>
{
if (listView.SelectedItems.Count > 0)
{
ListViewItem selectedItem = listView.SelectedItems[0];
AppConfig? app = selectedItem.Tag as AppConfig;
if (app == null) return;
if (_appStatusManager.GetAppStatus(app.Id) == AppStatus.UpdateAvailable)
{
if (string.IsNullOrEmpty(app.DownloadUrl))
{
MessageBox.Show("No download URL specified for this app.");
return;
}
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = app.DownloadUrl,
UseShellExecute = true
});
} else
{
var form = _formFactory.CreateAppDetailsForm(app, true, onAppChangedHandler);
form.FormClosed += (s2, e) =>
{
UpdateListView();
};
UpdateListView();
form.ShowDialog();
form.BringToFront();
}
}
};
return listView;
}
private FlowLayoutPanel GetButtonsPanel() {
var buttonPanel = new FlowLayoutPanel { Dock = DockStyle.Fill };
var addButton = new Button { Text = "Add App" };
var editButton = new Button { Text = "Edit App", Enabled = false };
var deleteButton = new Button { Text = "Delete App", Enabled = false };
var recheckButton = new Button { Text = "Recheck Versions" };
addButton.Click += (s, e) =>
{
var form = _formFactory.CreateAppDetailsForm(null, true, onAppChangedHandler); // Use factory
form.ShowDialog();
};
editButton.Click += (s, e) =>
{
if (_listView.SelectedItems.Count > 0)
{
ListViewItem selectedItem = _listView.SelectedItems[0];
AppConfig app = selectedItem.Tag as AppConfig;
var form = _formFactory.CreateAppDetailsForm(app, true, onAppChangedHandler); // Use factory
form.ShowDialog();
}
};
deleteButton.Click += (s, e) =>
{
if (_listView.SelectedItems.Count > 0 && _listView.SelectedItems[0].Tag != null)
{
_appsManager.Delete((_listView.SelectedItems[0].Tag as AppConfig).Id);
UpdateListView();
OnConfigChanged?.Invoke(this, EventArgs.Empty);
}
};
_listView.SelectedIndexChanged += (s, e) =>
{
editButton.Enabled = deleteButton.Enabled = _listView.SelectedItems.Count > 0;
};
// Add recheck logic here
recheckButton.Click += async (s, e) =>
{
recheckButton.Enabled = false;
OnRecheck.Invoke(this, "User initiated recheck from ConfigForm");
recheckButton.Enabled = true;
};
buttonPanel.Controls.AddRange(new[] { addButton, editButton, deleteButton, recheckButton });
return buttonPanel;
}
public void UpdateListView()
{
_apps = _appsManager.Load();
_listView.Items.Clear();
foreach (var app in _apps)
{
var item = new ListViewItem(app.Name);
item.Tag = app;
item.SubItems.Add(string.Join(", ", app.MsfsVersions));
try
{
var fsMod = _fsMods.FirstOrDefault(fs => fs.ShortName == "msfs2024");
// Pass the FsModPathConfig object directly, not its Path property
var currentVersion = app.CurrentVersion;
var latestVersion = app.LatestVersion;
var lastChecked = TimeUtils.ToFriendlyTime(app.LastCheckedAt);
item.SubItems.Add(currentVersion);
item.SubItems.Add(latestVersion);
item.SubItems.Add(lastChecked);
}
catch (Exception ex)
{
item.SubItems.Add($"Error: {ex.Message}");
}
switch (_appStatusManager.GetAppStatus(app.Id))
{
case AppStatus.UpdateAvailable:
item.ImageKey = "update";
break;
case AppStatus.Error:
item.ImageKey = "error";
break;
default:
item.ImageKey = "none";
break;
}
_listView.Items.Add(item);
}
Console.WriteLine($"UpdateListView item count: {_listView.Items.Count}");
}
private void InitializeMenu()
{
_menuStrip = new MenuStrip();
// Create top-level menu
var configMenu = new ToolStripMenuItem("Configuration");
// Add sub-menu items
var globalConfigItem = new ToolStripMenuItem("Global Settings");
globalConfigItem.Click += (s, e) => ShowGlobalConfigDialog();
var sourcesConfigItem = new ToolStripMenuItem("Sources");
sourcesConfigItem.Click += (s, e) => ShowSourcesConfigDialog();
var FlightSimsConfigItem = new ToolStripMenuItem("Flight Sims");
FlightSimsConfigItem.Click += (s, e) => MessageBox.Show("Flight Sims configuration dialog would open here.");
configMenu.DropDownItems.Add(globalConfigItem);
configMenu.DropDownItems.Add(sourcesConfigItem);
configMenu.DropDownItems.Add(FlightSimsConfigItem);
_menuStrip.Items.Add(configMenu);
// Add the menu to the form
Controls.Add(_menuStrip);
MainMenuStrip = _menuStrip;
}
private void ShowGlobalConfigDialog()
{
// Show your global config form/dialog here
var globalConfigForm = _formFactory.CreateGlobalConfigForm();
globalConfigForm.ShowDialog();
}
private void ShowSourcesConfigDialog()
{
EventHandler<string> onSourcesChanged = (s, e) => MessageBox.Show("Sources Changed");
var form = _formFactory.CreateSourcesConfigForm(onSourcesChanged);
form.ShowDialog();
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,110 @@
using ModVersionChecker.data.model;
using ModVersionChecker.managers.interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModVersionChecker.forms
{
// Simple editor form for SourceDef
public class SourceDetailForm : Form
{
private readonly IFormFactory _formFactory;
private readonly ISourcesDefManager _sourceManager;
public SourceDef SourceDef { get; set; }
public Boolean IsEditable => !string.IsNullOrWhiteSpace(SourceDef?.Id);
private TextBox _idField, _nameField, _typeField, _defaultsField;
private Button _okButton, _cancelButton;
public event EventHandler<string>? OnSourceChanged;
public SourceDetailForm(IFormFactory formFactory, ISourcesDefManager sourceManager)
{
_formFactory = formFactory ?? throw new ArgumentNullException(nameof(formFactory));
_sourceManager = sourceManager ?? throw new ArgumentNullException(nameof(sourceManager));
InitializeComponent();
_formFactory = formFactory;
}
private void InitializeComponent()
{
Text = "Edit SourceDef";
Size = new Size(400, 300);
StartPosition = FormStartPosition.CenterParent;
Padding = new Padding(10);
var layout = new TableLayoutPanel
{
Dock = DockStyle.Fill,
RowCount = 5,
ColumnCount = 2,
Padding = new Padding(10)
};
layout.Controls.Add(new Label { Text = "Id:", Width = 80 }, 0, 0);
_idField = new TextBox { Text = "", Width = 200 };
layout.Controls.Add(_idField, 1, 0);
layout.Controls.Add(new Label { Text = "Name:", Width = 80 }, 0, 1);
_nameField = new TextBox { Text = "", Width = 200 };
layout.Controls.Add(_nameField, 1, 1);
layout.Controls.Add(new Label { Text = "Type:", Width = 80 }, 0, 2);
_typeField = new TextBox { Text = "", Width = 200 };
layout.Controls.Add(_typeField, 1, 2);
layout.Controls.Add(new Label { Text = "Defaults (key=value, comma separated):", Width = 80 }, 0, 3);
_defaultsField = new TextBox { Text = "", Width = 200 };
layout.Controls.Add(_defaultsField, 1, 3);
var buttonPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, Dock = DockStyle.Fill };
_okButton = new Button { Text = "OK", DialogResult = DialogResult.OK };
_cancelButton = new Button { Text = "Cancel", DialogResult = DialogResult.Cancel };
buttonPanel.Controls.Add(_okButton);
buttonPanel.Controls.Add(_cancelButton);
layout.Controls.Add(buttonPanel, 0, 4);
layout.SetColumnSpan(buttonPanel, 2);
Controls.Add(layout);
_okButton.Click += (s, e) =>
{
SourceDef.Id = _idField.Text.Trim();
SourceDef.Name = _nameField.Text.Trim();
SourceDef.Type = _typeField.Text.Trim();
SourceDef.Defaults = ParseDefaults(_defaultsField.Text);
DialogResult = DialogResult.OK;
Close();
};
_cancelButton.Click += (s, e) => { DialogResult = DialogResult.Cancel; Close(); };
}
public void UpdateFields()
{
if (SourceDef != null)
{
_idField.Text = SourceDef.Id;
_nameField.Text = SourceDef.Name;
_typeField.Text = SourceDef.Type;
_defaultsField.Text = string.Join(", ", SourceDef.Defaults.Select(d => $"{d.Key}={d.Value}"));
}
}
private Dictionary<string, string> ParseDefaults(string text)
{
var dict = new Dictionary<string, string>();
var pairs = text.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var pair in pairs)
{
var kv = pair.Split(new[] { '=' }, 2);
if (kv.Length == 2)
dict[kv[0].Trim()] = kv[1].Trim();
}
return dict;
}
}
}

View File

@@ -0,0 +1,136 @@
using ModVersionChecker.data.model;
using ModVersionChecker.managers.interfaces;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace ModVersionChecker.forms
{
public class SourcesConfigForm : Form
{
private List<SourceDef> _sourceDefs;
private ListView _listView;
private Button _addButton, _editButton, _deleteButton, _closeButton;
private TableLayoutPanel _mainLayout;
private readonly ISourcesDefManager _sourcesManager;
private readonly IFormFactory _formFactory;
public event EventHandler<string>? OnSourcesChanged;
public List<SourceDef> SourceDefs => _sourceDefs;
public SourcesConfigForm(IFormFactory formFactory, ISourcesDefManager sourcesManager)
{
_sourcesManager = sourcesManager ?? throw new ArgumentNullException(nameof(sourcesManager));
_formFactory = formFactory ?? throw new ArgumentNullException(nameof(formFactory));
_sourceDefs = _sourcesManager.List() ?? new List<SourceDef>();
Padding = new Padding(20);
InitializeComponent();
}
private void InitializeComponent()
{
Text = "Source Definitions";
Size = new Size(800, 400);
StartPosition = FormStartPosition.CenterParent;
_mainLayout = new TableLayoutPanel
{
Dock = DockStyle.Fill,
RowCount = 2,
ColumnCount = 1,
Padding = new Padding(10)
};
_mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 80));
_mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 20));
_listView = new ListView
{
Dock = DockStyle.Fill,
View = View.Details,
FullRowSelect = true,
MultiSelect = false,
GridLines = true
};
_listView.Columns.Add("Id", 100);
_listView.Columns.Add("Name", 150);
_listView.Columns.Add("Type", 100);
_listView.Columns.Add("Defaults", -2);
UpdateListView();
_mainLayout.Controls.Add(_listView, 0, 0);
var buttonPanel = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.LeftToRight };
_addButton = new Button { Text = "Add" };
_editButton = new Button { Text = "Edit", Enabled = false };
_deleteButton = new Button { Text = "Delete", Enabled = false };
_closeButton = new Button { Text = "Close", DialogResult = DialogResult.OK };
_addButton.Click += (s, e) => AddSourceDef();
_editButton.Click += (s, e) => EditSourceDef();
_deleteButton.Click += (s, e) => DeleteSourceDef();
_closeButton.Click += (s, e) => Close();
_listView.SelectedIndexChanged += (s, e) =>
{
bool hasSelection = _listView.SelectedItems.Count > 0;
_editButton.Enabled = hasSelection;
_deleteButton.Enabled = hasSelection;
};
buttonPanel.Controls.AddRange(new Control[] { _addButton, _editButton, _deleteButton, _closeButton });
_mainLayout.Controls.Add(buttonPanel, 0, 1);
Controls.Add(_mainLayout);
}
private void UpdateListView()
{
_listView.Items.Clear();
foreach (var src in _sourceDefs)
{
var item = new ListViewItem(src.Id);
item.SubItems.Add(src.Name);
item.SubItems.Add(src.Type);
item.SubItems.Add(string.Join(", ", src.Defaults.Select(d => $"{d.Key}={d.Value}")));
item.Tag = src;
_listView.Items.Add(item);
}
}
private void AddSourceDef()
{
EventHandler<string>? handler = (s, e) => MessageBox.Show("Source Changed");
var editor = _formFactory.CreateSourceDetailForm(null, handler);
if (editor.ShowDialog() == DialogResult.OK)
{
_sourceDefs.Add(editor.SourceDef);
UpdateListView();
}
}
private void EditSourceDef()
{
if (_listView.SelectedItems.Count == 0) return;
var src = _listView.SelectedItems[0].Tag as SourceDef;
EventHandler<string>? handler = (s, e) => MessageBox.Show("Source Changed");
var editor = _formFactory.CreateSourceDetailForm(src, handler);
if (editor.ShowDialog() == DialogResult.OK)
{
//int idx = _sourceDefs.IndexOf(src);
//_sourceDefs[idx] = editor.SourceDef;
//UpdateListView();
}
}
private void DeleteSourceDef()
{
if (_listView.SelectedItems.Count == 0) return;
var src = _listView.SelectedItems[0].Tag as SourceDef;
_sourceDefs.Remove(src);
UpdateListView();
}
}
}