Implement mutually exclusive logic for thumbnail display modes in DataModel and MainForm. Add ThumbnailMode property for authoritative state. Replace radio button bindings with event handlers. Synchronize UI with model changes. Explicitly map thumbnail flags to PicSettings. Force WinForms startup, disabling WPF branch.
880 lines
No EOL
34 KiB
C#
880 lines
No EOL
34 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Drawing.Text;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using ImageCatalog_2;
|
|
using ImageCatalog_2.Commands;
|
|
using ImageCatalog_2.Services;
|
|
using MaddoShared;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace ImageCatalog;
|
|
|
|
public partial class MainForm
|
|
{
|
|
private readonly DataModel Model;
|
|
|
|
private readonly ILogger<MainForm> _logger;
|
|
|
|
private readonly ParametriSetup _parametriSetup;
|
|
private readonly PicSettings _picSettings;
|
|
// Prevent re-entrant updates between UI events and model PropertyChanged handling
|
|
private bool _suppressRadioUpdates = false;
|
|
private bool _transparentDialogOpen = false;
|
|
|
|
public MainForm(DataModel model, ImageCreationStuff imageCreationStuff, PicSettings picSettings,
|
|
ParametriSetup parametriSetup, ILogger<MainForm> logger)
|
|
{
|
|
Model = model;
|
|
_parametriSetup = parametriSetup;
|
|
_picSettings = picSettings;
|
|
_logger = logger;
|
|
|
|
_logger.LogDebug("Start");
|
|
|
|
InitializeComponent();
|
|
// Set this form as the control for thread marshalling in the DataModel
|
|
Model.SetControl(this);
|
|
|
|
// Ensure the designer data bindings have a concrete DataSource immediately so
|
|
// that UI controls (radio buttons) reflect the ViewModel state and propagate
|
|
// user changes back to the ViewModel.
|
|
bindingSource1.DataSource = Model;
|
|
|
|
BindControls();
|
|
|
|
// The designer originally bound the radio buttons to boolean helpers on the ViewModel.
|
|
// Those two separate bindings can fight with each other. Clear designer bindings and
|
|
// wire explicit Click handlers that update the single authoritative property
|
|
// `Model.ImageLibrary`. Also keep a PropertyChanged listener to reflect external
|
|
// changes back into the radio buttons.
|
|
rdbLibrary1.DataBindings.Clear();
|
|
rdbLibrary2.DataBindings.Clear();
|
|
|
|
// Initialize radio state from model
|
|
rdbLibrary1.Checked = Model.UseSystemGraphics;
|
|
rdbLibrary2.Checked = Model.UseImageSharp;
|
|
|
|
// Use Click handlers (not CheckedChanged) to avoid competing binding updates
|
|
rdbLibrary1.Click += (_, _) =>
|
|
{
|
|
if (_suppressRadioUpdates) return;
|
|
if (Model.ImageLibrary != "System.Graphics")
|
|
Model.ImageLibrary = "System.Graphics";
|
|
};
|
|
rdbLibrary2.Click += (_, _) =>
|
|
{
|
|
if (_suppressRadioUpdates) return;
|
|
if (Model.ImageLibrary != "ImageSharp")
|
|
Model.ImageLibrary = "ImageSharp";
|
|
};
|
|
|
|
// Watch for model changes so we can reflect external updates
|
|
Model.PropertyChanged += Model_PropertyChanged;
|
|
|
|
// Fix thumbnail radio buttons: clear designer bindings to avoid binding vs click conflicts
|
|
RadioButton3.DataBindings.Clear();
|
|
RadioButton4.DataBindings.Clear();
|
|
RadioButton5.DataBindings.Clear();
|
|
RadioButton6.DataBindings.Clear();
|
|
RadioButton7.DataBindings.Clear();
|
|
|
|
// Initialize radio state from model (thumbnail options)
|
|
try
|
|
{
|
|
_suppressRadioUpdates = true;
|
|
RadioButton3.Checked = Model.AddTextToThumbnails;
|
|
RadioButton4.Checked = Model.AddTimeToThumbnails;
|
|
RadioButton6.Checked = Model.ShowPhotoNumber;
|
|
RadioButton7.Checked = Model.AddNumberAndTimeToThumbnails;
|
|
RadioButton5.Checked = Model.AddRaceTimeToThumbnails;
|
|
}
|
|
finally
|
|
{
|
|
_suppressRadioUpdates = false;
|
|
}
|
|
|
|
// Use CheckedChanged handlers to set the authoritative ThumbnailMode on the model
|
|
RadioButton3.CheckedChanged += (s, ev) =>
|
|
{
|
|
if (_suppressRadioUpdates) return;
|
|
if (RadioButton3.Checked) Model.ThumbnailMode = "Text";
|
|
};
|
|
RadioButton4.CheckedChanged += (s, ev) =>
|
|
{
|
|
if (_suppressRadioUpdates) return;
|
|
if (RadioButton4.Checked) Model.ThumbnailMode = "Time";
|
|
};
|
|
RadioButton6.CheckedChanged += (s, ev) =>
|
|
{
|
|
if (_suppressRadioUpdates) return;
|
|
if (RadioButton6.Checked) Model.ThumbnailMode = "Number";
|
|
};
|
|
RadioButton7.CheckedChanged += (s, ev) =>
|
|
{
|
|
if (_suppressRadioUpdates) return;
|
|
if (RadioButton7.Checked) Model.ThumbnailMode = "NumberAndTime";
|
|
};
|
|
RadioButton5.CheckedChanged += (s, ev) =>
|
|
{
|
|
if (_suppressRadioUpdates) return;
|
|
if (RadioButton5.Checked) Model.ThumbnailMode = "RaceTime";
|
|
};
|
|
|
|
// Save user preferences on form close instead of immediately when dialogs are used
|
|
this.FormClosing += MainForm_FormClosing;
|
|
|
|
// Wire up 'Open folder in Explorer' buttons
|
|
btnOpenSourceFolder.Click += BtnOpenSourceFolder_Click;
|
|
btnOpenDestFolder.Click += BtnOpenDestFolder_Click;
|
|
|
|
// Show currently selected color in small PictureBox3
|
|
PictureBox3.BackColor = ColorTranslator.FromHtml(Model.TransparentColor);
|
|
|
|
// Version label is data-bound to DataModel.AppVersion; DataModel is populated with the version via DI
|
|
}
|
|
|
|
|
|
|
|
private void RdbLibrary_CheckedChanged(object? sender, EventArgs e)
|
|
{
|
|
// Keep behavior simple: when a radio button becomes checked, update the ViewModel
|
|
// so that the designer binding and PicSettings stay in sync.
|
|
if (sender is RadioButton rb && rb.Checked)
|
|
{
|
|
_logger?.LogDebug("Radio library changed: {RadioName}", rb.Name);
|
|
if (rb == rdbLibrary2)
|
|
{
|
|
Model.ImageLibrary = "ImageSharp";
|
|
}
|
|
else if (rb == rdbLibrary1)
|
|
{
|
|
Model.ImageLibrary = "System.Graphics";
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Model_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName is null) return;
|
|
if (e.PropertyName == nameof(Model.ImageLibrary) || e.PropertyName == nameof(Model.UseImageSharp) || e.PropertyName == nameof(Model.UseSystemGraphics))
|
|
{
|
|
_logger?.LogDebug("Model property changed: {Property} => ImageLibrary={ImageLibrary}, PicSettings.Provider={Provider}", e.PropertyName, Model.ImageLibrary, _picSettings.ImageCreatorProvider);
|
|
|
|
// Reflect authoritative model value into the radio buttons in a thread-safe, re-entrancy-safe way
|
|
try
|
|
{
|
|
_suppressRadioUpdates = true;
|
|
rdbLibrary1.Checked = Model.UseSystemGraphics;
|
|
rdbLibrary2.Checked = Model.UseImageSharp;
|
|
}
|
|
finally
|
|
{
|
|
_suppressRadioUpdates = false;
|
|
}
|
|
}
|
|
|
|
// Thumbnail mode changes - reflect back to radio buttons
|
|
if (e.PropertyName == nameof(Model.ThumbnailMode) ||
|
|
e.PropertyName == nameof(Model.AddTextToThumbnails) ||
|
|
e.PropertyName == nameof(Model.AddTimeToThumbnails) ||
|
|
e.PropertyName == nameof(Model.ShowPhotoNumber) ||
|
|
e.PropertyName == nameof(Model.AddNumberAndTimeToThumbnails) ||
|
|
e.PropertyName == nameof(Model.AddRaceTimeToThumbnails))
|
|
{
|
|
try
|
|
{
|
|
_suppressRadioUpdates = true;
|
|
RadioButton3.Checked = Model.AddTextToThumbnails;
|
|
RadioButton4.Checked = Model.AddTimeToThumbnails;
|
|
RadioButton6.Checked = Model.ShowPhotoNumber;
|
|
RadioButton7.Checked = Model.AddNumberAndTimeToThumbnails;
|
|
RadioButton5.Checked = Model.AddRaceTimeToThumbnails;
|
|
}
|
|
finally
|
|
{
|
|
_suppressRadioUpdates = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void BindControls()
|
|
{
|
|
// Bind buttons to ViewModel commands using command binding
|
|
_btnCreaCatalogoAsync.BindCommand(Model.ProcessImagesCommand);
|
|
// Note: `button1` control does not exist in the designer. Use the primary create button only.
|
|
_Button2.BindCommand(Model.SelectSourceFolderCommand);
|
|
_Button3.BindCommand(Model.SelectDestinationFolderCommand);
|
|
_Button4.BindCommand(Model.SelectLogoFileCommand);
|
|
_Button5.BindCommand(Model.SaveSettingsCommand);
|
|
_Button6.BindCommand(Model.LoadSettingsCommand);
|
|
_Button8.BindCommand(Model.SelectColorCommand);
|
|
// Bind the transparency chooser button/command
|
|
btnSetTransparency.BindCommand(Model.SelectTransparentColorCommand);
|
|
|
|
// Subscribe to ViewModel events for UI dialogs (these need UI context)
|
|
Model.SelectSourceFolderRequested += OnSelectSourceFolderRequested;
|
|
Model.SelectDestinationFolderRequested += OnSelectDestinationFolderRequested;
|
|
Model.SelectLogoFileRequested += OnSelectLogoFileRequested;
|
|
Model.SaveSettingsRequested += OnSaveSettingsRequested;
|
|
Model.LoadSettingsRequested += OnLoadSettingsRequested;
|
|
Model.SelectColorRequested += OnSelectColorRequested;
|
|
Model.SelectTransparentColorRequested += OnSelectTransparentColorRequested;
|
|
// Show message requests (from ViewModel validation)
|
|
Model.ShowMessageRequested += OnShowMessageRequested;
|
|
}
|
|
|
|
private void OnSelectTransparentColorRequested(object? sender, EventArgs e)
|
|
{
|
|
// Ensure UI thread
|
|
if (InvokeRequired)
|
|
{
|
|
Invoke(new Action<object, EventArgs>(OnSelectTransparentColorRequested), sender, e as EventArgs ?? EventArgs.Empty);
|
|
return;
|
|
}
|
|
// Prevent re-entrancy: if the dialog is already open, ignore subsequent requests
|
|
if (_transparentDialogOpen) return;
|
|
|
|
_transparentDialogOpen = true;
|
|
var dlg = new ColorDialog { AllowFullOpen = true };
|
|
try
|
|
{
|
|
dlg.Color = ColorTranslator.FromHtml(Model.TransparentColor);
|
|
}
|
|
catch { }
|
|
try
|
|
{
|
|
if (dlg.ShowDialog() == DialogResult.OK)
|
|
{
|
|
Model.TransparentColor = ColorTranslator.ToHtml(dlg.Color);
|
|
PictureBox3.BackColor = dlg.Color;
|
|
|
|
// Update preview if logo exists
|
|
if (!string.IsNullOrWhiteSpace(Model.LogoFile) && File.Exists(Model.LogoFile))
|
|
{
|
|
UpdateLogoPictureBox(Model.LogoFile);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_transparentDialogOpen = false;
|
|
}
|
|
}
|
|
|
|
private void BtnSetTransparency_Click(object? sender, EventArgs e)
|
|
{
|
|
Model.SelectTransparentColorCommand.Execute(null);
|
|
}
|
|
|
|
private void OnShowMessageRequested(object? sender, Tuple<string, string, MessageBoxIcon> args)
|
|
{
|
|
if (args is null) return;
|
|
// Ensure call on UI thread
|
|
if (InvokeRequired)
|
|
{
|
|
Invoke(new Action(() => OnShowMessageRequested(sender, args)));
|
|
return;
|
|
}
|
|
|
|
MessageBox.Show(this, args.Item1, args.Item2, MessageBoxButtons.OK, args.Item3);
|
|
}
|
|
|
|
private void SetDefaults()
|
|
{
|
|
// Bind ComboBoxes to Model using proper data binding
|
|
ComboBox1.DataSource = new List<string>(Model.VerticalPositions);
|
|
ComboBox1.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.VerticalPosition),
|
|
false, DataSourceUpdateMode.OnPropertyChanged));
|
|
|
|
ComboBox2.DataSource = new List<string>(Model.HorizontalAlignments);
|
|
ComboBox2.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.HorizontalAlignment),
|
|
false, DataSourceUpdateMode.OnPropertyChanged));
|
|
|
|
ComboBox3.DataSource = new List<string>(Model.AvailableFonts);
|
|
ComboBox3.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.FontName),
|
|
false, DataSourceUpdateMode.OnPropertyChanged));
|
|
|
|
ComboBox4.DataSource = new List<string>(Model.HorizontalAlignments);
|
|
ComboBox4.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoHorizontalPosition),
|
|
false, DataSourceUpdateMode.OnPropertyChanged));
|
|
|
|
ComboBox5.DataSource = new List<string> { "Alto", "Centro", "Basso" };
|
|
ComboBox5.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoVerticalPosition),
|
|
false, DataSourceUpdateMode.OnPropertyChanged));
|
|
|
|
// Bind progress bar and status labels
|
|
ProgressBar1.DataBindings.Add(new Binding("Maximum", bindingSource1, nameof(Model.ProgressBarMaximum),
|
|
false, DataSourceUpdateMode.OnPropertyChanged));
|
|
ProgressBar1.DataBindings.Add(new Binding("Value", bindingSource1, nameof(Model.ProgressBarValue),
|
|
false, DataSourceUpdateMode.OnPropertyChanged));
|
|
|
|
Label18.DataBindings.Add(new Binding("Text", bindingSource1, nameof(Model.ProcessedImagesCount),
|
|
false, DataSourceUpdateMode.OnPropertyChanged));
|
|
lblFotoTotaliNum.DataBindings.Add(new Binding("Text", bindingSource1, nameof(Model.TotalImagesCount),
|
|
false, DataSourceUpdateMode.OnPropertyChanged));
|
|
Label10.DataBindings.Add(new Binding("Text", bindingSource1, nameof(Model.ProcessingStatus),
|
|
false, DataSourceUpdateMode.OnPropertyChanged));
|
|
|
|
// Bind transparency model properties to UI
|
|
chkUseTransparentColor.DataBindings.Add(new Binding("Checked", bindingSource1, nameof(Model.UseTransparentColor), false, DataSourceUpdateMode.OnPropertyChanged));
|
|
// Show currently selected color in PictureBox3
|
|
PictureBox3.Visible = false;
|
|
if (!string.IsNullOrWhiteSpace(Model.TransparentColor))
|
|
{
|
|
try
|
|
{
|
|
PictureBox3.BackColor = ColorTranslator.FromHtml(Model.TransparentColor);
|
|
PictureBox3.Visible = true;
|
|
}
|
|
catch
|
|
{
|
|
PictureBox3.Visible = false;
|
|
}
|
|
}
|
|
|
|
// When logo file changes, update preview
|
|
Model.PropertyChanged += (s, e) =>
|
|
{
|
|
if (e.PropertyName == nameof(Model.LogoFile) || e.PropertyName == nameof(Model.UseTransparentColor) || e.PropertyName == nameof(Model.TransparentColor))
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(Model.LogoFile) && System.IO.File.Exists(Model.LogoFile))
|
|
{
|
|
UpdateLogoPictureBox(Model.LogoFile);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Bind transparent color hex and show color in PictureBox3
|
|
// Bind UseTransparentColor checkbox (designer control named CheckBox5 used for AddLogo earlier, add new binding control exists in designer)
|
|
// Use PictureBox3 to display color value
|
|
var colorBinding = new Binding("BackColor", bindingSource1, nameof(Model.TransparentColor), true, DataSourceUpdateMode.OnPropertyChanged);
|
|
colorBinding.Format += (s, e) =>
|
|
{
|
|
try
|
|
{
|
|
e.Value = ColorTranslator.FromHtml(e.Value?.ToString() ?? "#FFFFFF");
|
|
}
|
|
catch
|
|
{
|
|
e.Value = Color.White;
|
|
}
|
|
};
|
|
PictureBox3.DataBindings.Add(colorBinding);
|
|
|
|
// Bind checkbox for using color key transparency if such control exists (CheckBox5 was repurposed as AddLogo); create binding if available
|
|
try
|
|
{
|
|
// The designer has CheckBox5 for 'AddLogo'. We'll add a separate binding to a new control named CheckBoxUseTransparentColor if present.
|
|
var chk = this.Controls.Find("chkUseTransparentColor", true).FirstOrDefault() as CheckBox;
|
|
if (chk != null)
|
|
{
|
|
chk.DataBindings.Add(new Binding("Checked", bindingSource1, nameof(Model.UseTransparentColor), false, DataSourceUpdateMode.OnPropertyChanged));
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
|
|
private void Form1_Load(object sender, EventArgs e)
|
|
{
|
|
bindingSource1.DataSource = Model;
|
|
Application.EnableVisualStyles();
|
|
SetDefaults();
|
|
|
|
_logger.LogInformation("Programma Avviato");
|
|
// If settings were loaded before the form was shown, ensure the logo preview is updated
|
|
try
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(Model.LogoFile) && File.Exists(Model.LogoFile))
|
|
{
|
|
UpdateLogoPictureBox(Model.LogoFile);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogDebug(ex, "Failed to load logo during form load");
|
|
}
|
|
}
|
|
|
|
private string CalcTime(DateTime timeStart, DateTime timeStop, int numFoto)
|
|
{
|
|
long timediffH, timediffS;
|
|
long timediffM;
|
|
|
|
TimeSpan timeDiff = timeStop - timeStart;
|
|
|
|
timediffM = (int)timeDiff.TotalMinutes;
|
|
timediffS = (int)timeDiff.TotalSeconds;
|
|
timediffH = (int)timeDiff.TotalHours;
|
|
|
|
// timediffM = DateAndTime.DateDiff(DateInterval.Minute, timeStart, timeStop);
|
|
// timediffS = DateAndTime.DateDiff(DateInterval.Second, timeStart, timeStop);
|
|
// timediffH = DateAndTime.DateDiff(DateInterval.Hour, timeStart, timeStop);
|
|
|
|
double fotoSec = numFoto / (double)timediffS;
|
|
double fotoMin = numFoto / (double)timediffM;
|
|
double fotoOra = numFoto / (double)timediffH;
|
|
string s = "S: " + timediffS.ToString() + "; F/s: " +
|
|
fotoSec.ToString(
|
|
"0.000"); // + " F/m: " + fotoMin.ToString("0.00") + " F/h: " + fotoOra.ToString("0.00")
|
|
return s;
|
|
}
|
|
|
|
private string SelectFolder(string startingFolder)
|
|
{
|
|
var dialog = new FolderBrowserDialog();
|
|
dialog.InitialDirectory = startingFolder;
|
|
if (dialog.ShowDialog() != DialogResult.OK) return string.Empty;
|
|
|
|
return FixPath(dialog.SelectedPath);
|
|
}
|
|
|
|
private string FixPath(string path)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(path))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
// Trim leading/trailing quotes
|
|
path = path.Trim().Trim('"');
|
|
|
|
// Normalize directory separators
|
|
path = path.Replace('/', Path.DirectorySeparatorChar)
|
|
.Replace('\\', Path.DirectorySeparatorChar);
|
|
|
|
// Remove trailing separators then add one back
|
|
path = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
|
|
|
return path;
|
|
}
|
|
|
|
private void OnSelectSourceFolderRequested(object sender, EventArgs e)
|
|
{
|
|
// Prefer model value; if empty fall back to last-used value stored in user prefs
|
|
var starting = !string.IsNullOrWhiteSpace(Model.SourcePath)
|
|
? Model.SourcePath
|
|
: _parametriSetup.LeggiParametroString("LastSourceFolder");
|
|
|
|
var dialogResult = SelectFolder(starting);
|
|
if (!string.IsNullOrWhiteSpace(dialogResult))
|
|
{
|
|
Model.SourcePath = dialogResult;
|
|
_parametriSetup.AggiornaParametro("LastSourceFolder", dialogResult);
|
|
_parametriSetup.SalvaParametriSetup();
|
|
}
|
|
}
|
|
|
|
private void BtnOpenSourceFolder_Click(object? sender, EventArgs e)
|
|
{
|
|
// Prefer the model value but fall back to the textbox if needed
|
|
var path = string.IsNullOrWhiteSpace(Model.SourcePath) ? txtSorgente.Text : Model.SourcePath;
|
|
OpenFolder(path);
|
|
}
|
|
|
|
private void BtnOpenDestFolder_Click(object? sender, EventArgs e)
|
|
{
|
|
var path = string.IsNullOrWhiteSpace(Model.DestinationPath) ? txtDestinazione.Text : Model.DestinationPath;
|
|
OpenFolder(path);
|
|
}
|
|
|
|
private void OpenFolder(string? path)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(path))
|
|
{
|
|
MessageBox.Show(this, "Folder path is empty.", "Open Folder", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
return;
|
|
}
|
|
|
|
path = path.Trim().Trim('"');
|
|
|
|
try
|
|
{
|
|
if (File.Exists(path))
|
|
{
|
|
// If a file was provided, open its folder and select it
|
|
Process.Start("explorer.exe", $"/select,\"{path}\"");
|
|
return;
|
|
}
|
|
|
|
if (Directory.Exists(path))
|
|
{
|
|
Process.Start(new ProcessStartInfo
|
|
{
|
|
FileName = path,
|
|
UseShellExecute = true
|
|
});
|
|
return;
|
|
}
|
|
|
|
MessageBox.Show(this, $"Folder does not exist: {path}", "Open Folder", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogError(ex, "Failed to open folder {Path}", path);
|
|
MessageBox.Show(this, $"Failed to open folder: {ex.Message}", "Open Folder", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
}
|
|
|
|
private void OnSelectDestinationFolderRequested(object sender, EventArgs e)
|
|
{
|
|
var starting = !string.IsNullOrWhiteSpace(Model.DestinationPath)
|
|
? Model.DestinationPath
|
|
: _parametriSetup.LeggiParametroString("LastDestinationFolder");
|
|
|
|
var dialogResult = SelectFolder(starting);
|
|
if (!string.IsNullOrWhiteSpace(dialogResult))
|
|
{
|
|
Model.DestinationPath = dialogResult;
|
|
_parametriSetup.AggiornaParametro("LastDestinationFolder", dialogResult);
|
|
_parametriSetup.SalvaParametriSetup();
|
|
}
|
|
}
|
|
|
|
private void OnSelectLogoFileRequested(object sender, EventArgs e)
|
|
{
|
|
var dialog = new OpenFileDialog();
|
|
dialog.Filter = "Image Files|*.jpg;*.jpeg;*.png;*.bmp;*.gif";
|
|
if (!string.IsNullOrWhiteSpace(Model.LogoFile))
|
|
{
|
|
dialog.FileName = Model.LogoFile;
|
|
}
|
|
else
|
|
{
|
|
var lastLogoFolder = _parametriSetup.LeggiParametroString("LastLogoFolder");
|
|
if (!string.IsNullOrWhiteSpace(lastLogoFolder) && Directory.Exists(lastLogoFolder))
|
|
{
|
|
dialog.InitialDirectory = lastLogoFolder;
|
|
}
|
|
}
|
|
|
|
if (dialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
Model.LogoFile = dialog.FileName;
|
|
UpdateLogoPictureBox(Model.LogoFile);
|
|
try
|
|
{
|
|
var folder = Path.GetDirectoryName(dialog.FileName) ?? string.Empty;
|
|
if (!string.IsNullOrWhiteSpace(folder))
|
|
{
|
|
_parametriSetup.AggiornaParametro("LastLogoFolder", folder);
|
|
_parametriSetup.SalvaParametriSetup();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore preferences save failures
|
|
}
|
|
}
|
|
}
|
|
|
|
private async void OnSaveSettingsRequested(object sender, string e)
|
|
{
|
|
var saveDialog = new SaveFileDialog
|
|
{
|
|
Filter = "Setup (*.xml)|*.xml|All valid files (*.*)|*.*",
|
|
FilterIndex = 0,
|
|
RestoreDirectory = true
|
|
};
|
|
var lastSettings = _parametriSetup.LeggiParametroString("LastSettingsFolder");
|
|
if (!string.IsNullOrWhiteSpace(lastSettings) && Directory.Exists(lastSettings))
|
|
saveDialog.InitialDirectory = lastSettings;
|
|
|
|
if (saveDialog.ShowDialog() != DialogResult.OK) return;
|
|
|
|
await Model.SaveSettingsToFileAsync(saveDialog.FileName);
|
|
Text = "Image Catalog - " + Path.GetFileName(saveDialog.FileName);
|
|
|
|
try
|
|
{
|
|
var folder = Path.GetDirectoryName(saveDialog.FileName) ?? string.Empty;
|
|
if (!string.IsNullOrWhiteSpace(folder))
|
|
{
|
|
_parametriSetup.AggiornaParametro("LastSettingsFolder", folder);
|
|
_parametriSetup.SalvaParametriSetup();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
private async void OnLoadSettingsRequested(object sender, string e)
|
|
{
|
|
var openDialog = new OpenFileDialog
|
|
{
|
|
Filter = "Setup (*.xml)|*.xml|All valid files (*.*)|*.*",
|
|
FilterIndex = 0,
|
|
RestoreDirectory = true
|
|
};
|
|
|
|
var lastSettings = _parametriSetup.LeggiParametroString("LastSettingsFolder");
|
|
if (!string.IsNullOrWhiteSpace(lastSettings) && Directory.Exists(lastSettings))
|
|
openDialog.InitialDirectory = lastSettings;
|
|
|
|
if (openDialog.ShowDialog() != DialogResult.OK) return;
|
|
|
|
try
|
|
{
|
|
await Model.LoadSettingsFromFileAsync(openDialog.FileName);
|
|
|
|
// Explicitly ensure UI is enabled after loading
|
|
Model.UiEnabled = true;
|
|
|
|
// If a logo path was stored in the settings, try to resolve and display it.
|
|
// The stored path may be absolute or relative to the settings file location.
|
|
try
|
|
{
|
|
var storedLogo = Model.LogoFile;
|
|
// Trim whitespace and surrounding quotes which may be present in saved settings
|
|
if (!string.IsNullOrWhiteSpace(storedLogo))
|
|
{
|
|
storedLogo = storedLogo.Trim();
|
|
storedLogo = storedLogo.Trim('"');
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(storedLogo))
|
|
{
|
|
string resolved = storedLogo;
|
|
// If not rooted, try to resolve relative to the settings file folder
|
|
var settingsFolder = Path.GetDirectoryName(openDialog.FileName) ?? string.Empty;
|
|
if (!Path.IsPathRooted(resolved) && !string.IsNullOrWhiteSpace(settingsFolder))
|
|
{
|
|
var candidate = Path.Combine(settingsFolder, resolved);
|
|
if (File.Exists(candidate)) resolved = candidate;
|
|
}
|
|
|
|
// If rooted but file doesn't exist, try filename near settings file
|
|
if (!File.Exists(resolved) && !string.IsNullOrWhiteSpace(settingsFolder))
|
|
{
|
|
var candidate2 = Path.Combine(settingsFolder, Path.GetFileName(resolved));
|
|
if (File.Exists(candidate2)) resolved = candidate2;
|
|
}
|
|
|
|
if (File.Exists(resolved))
|
|
{
|
|
// Update the model so data-bound controls reflect the resolved absolute path
|
|
Model.LogoFile = resolved;
|
|
UpdateLogoPictureBox(resolved);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogDebug(ex, "Error resolving logo path after loading settings");
|
|
}
|
|
|
|
Text = "Image Catalog - " + Path.GetFileName(openDialog.FileName);
|
|
|
|
try
|
|
{
|
|
var folder = Path.GetDirectoryName(openDialog.FileName) ?? string.Empty;
|
|
if (!string.IsNullOrWhiteSpace(folder))
|
|
{
|
|
_parametriSetup.AggiornaParametro("LastSettingsFolder", folder);
|
|
_parametriSetup.SalvaParametriSetup();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore preferences save failures
|
|
}
|
|
|
|
_logger.LogInformation($"Settings loaded successfully from {openDialog.FileName}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error loading settings");
|
|
MessageBox.Show($"Error loading settings: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
}
|
|
|
|
private void MainForm_FormClosing(object? sender, FormClosingEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// Persist last-used dialogs paths (user preferences)
|
|
// These keys are managed independently from settings files
|
|
// and must be saved when the form closes.
|
|
_parametriSetup.AggiornaParametro("LastSourceFolder", Model.SourcePath ?? string.Empty);
|
|
_parametriSetup.AggiornaParametro("LastDestinationFolder", Model.DestinationPath ?? string.Empty);
|
|
_parametriSetup.AggiornaParametro("LastLogoFolder", Path.GetDirectoryName(Model.LogoFile ?? string.Empty) ?? string.Empty);
|
|
_parametriSetup.SalvaParametriSetup();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning(ex, "Failed to save user preferences on exit");
|
|
}
|
|
}
|
|
|
|
private void OnSelectColorRequested(object sender, EventArgs e)
|
|
{
|
|
var colorDialog = new ColorDialog
|
|
{
|
|
AllowFullOpen = true
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(Model.TextColorRGB))
|
|
{
|
|
try
|
|
{
|
|
colorDialog.Color = ColorTranslator.FromHtml(Model.TextColorRGB);
|
|
}
|
|
catch
|
|
{
|
|
// Invalid color, use default
|
|
}
|
|
}
|
|
|
|
if (colorDialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
Model.TextColorRGB = ColorTranslator.ToHtml(colorDialog.Color);
|
|
TextBox34.BackColor = colorDialog.Color;
|
|
}
|
|
}
|
|
|
|
private void UpdateLogoPictureBox(string logoPath)
|
|
{
|
|
try
|
|
{
|
|
// Load image via System.Drawing for preview so we can use MakeTransparent when requested
|
|
using var img = System.Drawing.Image.FromFile(logoPath);
|
|
|
|
System.Drawing.Bitmap previewBmp;
|
|
// If using color-key transparency and a color is selected, apply MakeTransparent for preview
|
|
if (Model.UseTransparentColor && !string.IsNullOrWhiteSpace(Model.TransparentColor))
|
|
{
|
|
try
|
|
{
|
|
var key = ColorTranslator.FromHtml(Model.TransparentColor);
|
|
previewBmp = new System.Drawing.Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
using (var g = System.Drawing.Graphics.FromImage(previewBmp))
|
|
{
|
|
g.Clear(System.Drawing.Color.Transparent);
|
|
g.DrawImage(img, 0, 0, img.Width, img.Height);
|
|
}
|
|
// Apply exact color-key transparency
|
|
previewBmp.MakeTransparent(key);
|
|
PictureBox3.BackColor = key;
|
|
PictureBox3.Visible = true;
|
|
}
|
|
catch
|
|
{
|
|
previewBmp = new System.Drawing.Bitmap(img);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
previewBmp = new System.Drawing.Bitmap(img);
|
|
}
|
|
|
|
// Resize preview to fit into PictureBox1 while preserving aspect ratio
|
|
// Resize preview to fit into PictureBox1 while preserving aspect ratio
|
|
var boxW = PictureBox1.ClientSize.Width > 0 ? PictureBox1.ClientSize.Width : 449;
|
|
var boxH = PictureBox1.ClientSize.Height > 0 ? PictureBox1.ClientSize.Height : 369;
|
|
var ratio = Math.Min((double)boxW / previewBmp.Width, (double)boxH / previewBmp.Height);
|
|
var destW = Math.Max(1, (int)(previewBmp.Width * ratio));
|
|
var destH = Math.Max(1, (int)(previewBmp.Height * ratio));
|
|
var scaled = new System.Drawing.Bitmap(destW, destH, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
using (var g = System.Drawing.Graphics.FromImage(scaled))
|
|
{
|
|
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
|
g.Clear(System.Drawing.Color.Transparent);
|
|
// Center the image in the PictureBox
|
|
var offsetX = Math.Max(0, (boxW - destW) / 2);
|
|
var offsetY = Math.Max(0, (boxH - destH) / 2);
|
|
using var canvas = new System.Drawing.Bitmap(boxW, boxH, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
using (var cg = System.Drawing.Graphics.FromImage(canvas))
|
|
{
|
|
cg.Clear(System.Drawing.Color.Transparent);
|
|
cg.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
|
cg.DrawImage(previewBmp, offsetX, offsetY, destW, destH);
|
|
}
|
|
g.DrawImage(canvas, 0, 0);
|
|
}
|
|
|
|
// Set PictureBox1 image (dispose previous)
|
|
var old = PictureBox1.Image;
|
|
PictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
|
|
PictureBox1.Image = scaled;
|
|
old?.Dispose();
|
|
previewBmp.Dispose();
|
|
}
|
|
catch
|
|
{
|
|
// Image loading failed, ignore
|
|
}
|
|
}
|
|
|
|
private void UpdateLogoPreviewWithColorKey(string logoPath, Color keyColor)
|
|
{
|
|
try
|
|
{
|
|
using var img = (Bitmap)Image.FromFile(logoPath);
|
|
// Create ARGB copy
|
|
var bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
using (var g = Graphics.FromImage(bmp))
|
|
{
|
|
g.DrawImage(img, 0, 0, img.Width, img.Height);
|
|
}
|
|
|
|
bmp.MakeTransparent(keyColor);
|
|
|
|
// Resize to PictureBox1 size similar to previous logic
|
|
Bitmap finalBmp;
|
|
if (bmp.Height >= bmp.Width)
|
|
{
|
|
finalBmp = new Bitmap(bmp, new Size((int)(160 * bmp.Width / (double)bmp.Height), 160));
|
|
}
|
|
else
|
|
{
|
|
finalBmp = new Bitmap(bmp, new Size(160, (int)(160 * bmp.Height / (double)bmp.Width)));
|
|
}
|
|
|
|
PictureBox1.Image = finalBmp;
|
|
}
|
|
catch
|
|
{
|
|
// ignore preview failures
|
|
}
|
|
}
|
|
|
|
private void setLabel18Text(string text)
|
|
{
|
|
if (Label18.InvokeRequired)
|
|
{
|
|
Label18.Invoke(new Action<string>(setLabel18Text), text);
|
|
}
|
|
else
|
|
{
|
|
Label18.Text = text;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class PicInfo
|
|
{
|
|
public DirectoryInfo DirSource, DirDest, DirDestStart;
|
|
public string NomeImmagine;
|
|
|
|
public PicInfo(DirectoryInfo Dir_Source, DirectoryInfo Dir_Dest, DirectoryInfo Dir_DestStart,
|
|
string Nome_Immagine)
|
|
{
|
|
DirSource = Dir_Source;
|
|
DirDest = Dir_Dest;
|
|
DirDestStart = Dir_DestStart;
|
|
NomeImmagine = Nome_Immagine;
|
|
}
|
|
} |