Dialogs now remember last-used folders for source, destination, logo, and settings files by storing these paths in a user preferences file under LocalApplicationData. Preferences are saved on form close, reducing unnecessary writes. SettingsService now uses a temporary ParametriSetup for settings files to avoid polluting user preferences. Error handling ensures preference save failures do not disrupt the user. This separation improves user experience and keeps user preferences distinct from project settings.
494 lines
No EOL
18 KiB
C#
494 lines
No EOL
18 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;
|
|
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;
|
|
|
|
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);
|
|
|
|
BindControls();
|
|
|
|
// 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;
|
|
|
|
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
|
_Label27.Text = $"Version: {version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
|
}
|
|
|
|
protected void BindControls()
|
|
{
|
|
// Bind buttons to ViewModel commands using command binding
|
|
_btnCreaCatalogoAsync.BindCommand(Model.ProcessImagesCommand);
|
|
button1.BindCommand(Model.ProcessImagesCommand);
|
|
_Button2.BindCommand(Model.SelectSourceFolderCommand);
|
|
_Button3.BindCommand(Model.SelectDestinationFolderCommand);
|
|
_Button4.BindCommand(Model.SelectLogoFileCommand);
|
|
_Button5.BindCommand(Model.SaveSettingsCommand);
|
|
_Button6.BindCommand(Model.LoadSettingsCommand);
|
|
_Button8.BindCommand(Model.SelectColorCommand);
|
|
|
|
// 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;
|
|
// Show message requests (from ViewModel validation)
|
|
Model.ShowMessageRequested += OnShowMessageRequested;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
|
|
private void Form1_Load(object sender, EventArgs e)
|
|
{
|
|
bindingSource1.DataSource = Model;
|
|
Application.EnableVisualStyles();
|
|
SetDefaults();
|
|
|
|
_logger.LogInformation("Programma Avviato");
|
|
}
|
|
|
|
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;
|
|
|
|
// Update logo preview if logo file exists
|
|
if (File.Exists(Model.LogoFile))
|
|
{
|
|
UpdateLogoPictureBox(Model.LogoFile);
|
|
}
|
|
|
|
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
|
|
{
|
|
PictureBox1.Image = Image.FromFile(logoPath);
|
|
if (PictureBox1.Image.Height >= PictureBox1.Image.Width)
|
|
{
|
|
PictureBox1.Height = 160;
|
|
PictureBox1.Width = (int)(160 * PictureBox1.Image.Width / (double)PictureBox1.Image.Height);
|
|
}
|
|
else
|
|
{
|
|
PictureBox1.Width = 160;
|
|
PictureBox1.Height = (int)(160 * PictureBox1.Image.Height / (double)PictureBox1.Image.Width);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Image loading failed, ignore
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
} |