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.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using ImageCatalog_2; using ImageCatalog_2.Services; using MaddoShared; using Microsoft.Extensions.Logging; namespace ImageCatalog; public partial class MainForm { private readonly DataModel Model; private readonly ILogger _logger; private readonly ImageCreationStuff _imageCreationService; private readonly ParametriSetup _parametriSetup; private readonly PicSettings _picSettings; public MainForm(DataModel model, ImageCreationStuff imageCreationStuff, PicSettings picSettings, ParametriSetup parametriSetup, ILogger logger) { Model = model; _imageCreationService = imageCreationStuff; _parametriSetup = parametriSetup; _picSettings = picSettings; _logger = logger; _logger.LogDebug("Start"); InitializeComponent(); BindControls(); // Subscribe to DataModel events Model.SelectSourceFolderRequested += OnSelectSourceFolderRequested; Model.SelectDestinationFolderRequested += OnSelectDestinationFolderRequested; Model.SelectLogoFileRequested += OnSelectLogoFileRequested; Model.SaveSettingsRequested += OnSaveSettingsRequested; Model.LoadSettingsRequested += OnLoadSettingsRequested; Model.SelectColorRequested += OnSelectColorRequested; var version = Assembly.GetExecutingAssembly().GetName().Version; _Label27.Text = $"Version: {version.Major}.{version.Minor}.{version.Build}.{version.Revision}"; _results = []; UiUpdateEvent += OnUiUpdateEvent; } protected void BindControls() { // Wire up buttons to ViewModel commands _Button2.Click += (s, e) => Model.SelectSourceFolderCommand.Execute(null); _Button3.Click += (s, e) => Model.SelectDestinationFolderCommand.Execute(null); _Button4.Click += (s, e) => Model.SelectLogoFileCommand.Execute(null); _Button5.Click += (s, e) => Model.SaveSettingsCommand.Execute(null); _Button6.Click += (s, e) => Model.LoadSettingsCommand.Execute(null); _Button8.Click += (s, e) => Model.SelectColorCommand.Execute(null); } private event EventHandler> UiUpdateEvent; delegate void SetTextCallback(Label target, string text); private void SetText(Label target, string text) { if (InvokeRequired) { var d = new SetTextCallback(SetText); this.Invoke(d, new object[] { target, text }); } else { target.Text = text; } } private delegate void SetProgressCallback(ProgressBar target, int amount, int maximum); private void SetProgress(ProgressBar target, int amount, int maximum) { if (InvokeRequired) { var d = new SetProgressCallback(SetProgress); this.Invoke(d, new object[] { target, amount, maximum }); } else { target.Maximum = maximum; target.Value = amount; } } private void OnUiUpdateEvent(object sender, Tuple args) { SetProgress(ProgressBar1, _results.Count, args.Item2); SetText(Label18, _results.Count.ToString()); SetText(Label10, args.Item1); SetText(lblFotoTotaliNum, args.Item2.ToString()); } private ConcurrentBag _results; private void SetDefaults() { // Bind ComboBoxes to Model using proper data binding ComboBox1.DataSource = new List(Model.VerticalPositions); ComboBox1.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.VerticalPosition), false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox2.DataSource = new List(Model.HorizontalAlignments); ComboBox2.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.HorizontalAlignment), false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox3.DataSource = new List(Model.AvailableFonts); ComboBox3.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.FontName), false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox4.DataSource = new List(Model.HorizontalAlignments); ComboBox4.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoHorizontalPosition), false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox5.DataSource = new List { "Alto", "Centro", "Basso" }; ComboBox5.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoVerticalPosition), false, DataSourceUpdateMode.OnPropertyChanged)); } private void Form1_Load(object sender, EventArgs e) { bindingSource1.DataSource = Model; Application.EnableVisualStyles(); SetDefaults(); _logger.LogInformation("Programma Avviato"); } private void FixPaths() { Model.SourcePath = FixPath(Model.SourcePath); Model.DestinationPath = FixPath(Model.DestinationPath); } 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 lockUI() { Model.UiEnabled = false; //TabControl1.Enabled = false; //Button5.Enabled = false; //Button6.Enabled = false; //btnCreaCatalogoAsync.Enabled = false; } private void unlockUI() { Model.UiEnabled = true; //TabControl1.Enabled = true; //Button5.Enabled = true; //Button6.Enabled = true; //btnCreaCatalogoAsync.Enabled = true; } 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 void OnSelectSourceFolderRequested(object sender, EventArgs e) { var dialogResult = SelectFolder(Model.SourcePath); if (!string.IsNullOrWhiteSpace(dialogResult)) { Model.SourcePath = dialogResult; } } private void OnSelectDestinationFolderRequested(object sender, EventArgs e) { var dialogResult = SelectFolder(Model.DestinationPath); if (!string.IsNullOrWhiteSpace(dialogResult)) { Model.DestinationPath = dialogResult; } } private void OnSelectLogoFileRequested(object sender, EventArgs e) { var dialog = new OpenFileDialog(); dialog.Filter = "Image Files|*.jpg;*.jpeg;*.png;*.bmp;*.gif"; if (Model.LogoFile.Length > 0) { dialog.FileName = Model.LogoFile; } if (dialog.ShowDialog() == DialogResult.OK) { Model.LogoFile = dialog.FileName; UpdateLogoPictureBox(Model.LogoFile); } } private async void OnSaveSettingsRequested(object sender, string e) { var saveDialog = new SaveFileDialog { Filter = "Setup (*.xml)|*.xml|All valid files (*.*)|*.*", FilterIndex = 0, RestoreDirectory = true }; if (saveDialog.ShowDialog() != DialogResult.OK) return; await Model.SaveSettingsToFileAsync(saveDialog.FileName); Text = "Image Catalog - " + Path.GetFileName(saveDialog.FileName); } private async void OnLoadSettingsRequested(object sender, string e) { var openDialog = new OpenFileDialog { Filter = "Setup (*.xml)|*.xml|All valid files (*.*)|*.*", FilterIndex = 0, RestoreDirectory = true }; 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); _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 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 SetPicSettings(string SourcePath, string DestPath) { _picSettings.DirectorySorgente = SourcePath; _picSettings.DirectoryDestinazione = DestPath; // Font and text settings from Model _picSettings.DimStandard = Model.FontSize; _picSettings.DimStandardMiniatura = Model.FontSizeThumbnail; _picSettings.IlFont = Model.FontName; _picSettings.Grassetto = Model.FontBold; _picSettings.Posizione = Model.VerticalPosition; _picSettings.Allineamento = Model.HorizontalAlignment; _picSettings.Trasparenza = Model.TextTransparency; _picSettings.Margine = Model.TextMargin; _picSettings.FontColoreRGB = ColorTranslator.FromHtml(Model.TextColorRGB); // Thumbnail settings from Model _picSettings.AltezzaSmall = Model.ThumbnailHeight; _picSettings.LarghezzaSmall = Model.ThumbnailWidth; _picSettings.Suffisso = Model.ThumbnailPrefix; _picSettings.CreaMiniature = Model.CreateThumbnails; _picSettings.JpegQualityMin = Model.JpegQualityThumbnail; _picSettings.DimMin = Model.FontSizeThumbnail; // Big photo settings from Model _picSettings.AltezzaBig = Model.PhotoBigHeight; _picSettings.LarghezzaBig = Model.PhotoBigWidth; _picSettings.FotoGrandeDimOrigina = Model.KeepOriginalDimensions; _picSettings.JpegQuality = Model.JpegQuality; _picSettings.Codice = Model.BigPhotoSuffix; // Logo settings from Model _picSettings.LogoAggiungi = Model.AddLogo; _picSettings.LogoNomeFile = Model.LogoFile; _picSettings.LogoAltezza = Model.LogoHeight; _picSettings.LogoLarghezza = Model.LogoWidth; _picSettings.LogoMargine = Model.LogoMargin.ToString(); _picSettings.LogoTrasparenza = Model.LogoTransparency.ToString(); _picSettings.LogoPosizioneH = Model.LogoHorizontalPosition; _picSettings.LogoPosizioneV = Model.LogoVerticalPosition; // Text content from Model _picSettings.TestoFirmaStart = Model.HorizontalText; _picSettings.TestoFirmaStartV = Model.VerticalText; // Vertical text settings from Model _picSettings.DimVert = Model.VerticalTextSize; _picSettings.MargVert = Model.VerticalTextMargin; // Boolean flags from Model _picSettings.UsaRotazioneAutomatica = Model.AutomaticRotation; _picSettings.UsaForzaJpg = Model.ForceJpeg; _picSettings.TestoNome = Model.ShowPhotoNumber; _picSettings.NomeData = Model.ShowDate; _picSettings.UsaOrarioTestoApplicare = Model.AddTime; _picSettings.UsaTempoGaraTestoApplicare = Model.AddRaceTime; _picSettings.OverwriteFiles = Model.OverwriteImages; // Additional settings from Model _picSettings.UsaOrarioMiniatura = Model.AddTimeToThumbnails; _picSettings.DataPartenza = Model.RaceStartDate; _picSettings.TestoOrario = Model.TimeLabel; _picSettings.TestoMin = Model.ShowFileNameOnThumbnails; // Thumbnail text options from Model _picSettings.AggiungiScritteMiniature = Model.AddTextToThumbnails; _picSettings.AggTempoGaraMin = Model.AddRaceTimeToThumbnails; _picSettings.AggNumTempMin = Model.AddNumberAndTimeToThumbnails; } private void setLabel18Text(string text) { if (Label18.InvokeRequired) { Label18.Invoke(new Action(setLabel18Text), text); } else { Label18.Text = text; } } private NumerazioneType GetNumerazioneEnum() { NumerazioneType numerazioneType; if (rdbNumProgressiva.Checked) { numerazioneType = NumerazioneType.Progressiva; } else { numerazioneType = NumerazioneType.Files; } return numerazioneType; } private async void Button1_Click(object sender, EventArgs e) { _logger.LogInformation("Avvio elaborazione..."); lockUI(); Model.MainToken?.Dispose(); Model.MainToken = new CancellationTokenSource(); var token = Model.MainToken.Token; // timeStart = TimeOfDay FixPaths(); Label10.Text = "Elaborazione in corso..."; lblFotoTotaliNum.Text = "0"; Label18.Text = "0"; Model.SpeedCounter = "-f/m"; SetPicSettings(Model.SourcePath, Model.DestinationPath); ProgressBar1.Minimum = 0; ProgressBar1.Step = 1; ProgressBar1.Value = 0; var imageCreationOptions = new ImageCreationStuff.Options { AggiornaSottodirectory = chkAggiornaSottodirectory.Checked, CreaSottocartelle = chkCreaSottocartelle.Checked, FilePerCartella = int.Parse(txtFilePerCartella.Text), SuffissoCartelle = txtSuffissoCartelle.Text, CifreContatore = int.Parse(txtCifreContatore.Text), NumerazioneType = GetNumerazioneEnum(), SourcePath = Model.SourcePath, DestinationPath = Model.DestinationPath, MaxThreads = Model.ThreadsCount, ChunksSize = Model.ChunkSize, LinearExecution = rdbVecchioMetodo.Checked }; try { _results = []; _currentAmount = 0; _previousAmount = 0; timer1.Tick += Timer1OnTick; timer1.Interval = 1000 * 60; timer1.Enabled = true; var time = await _imageCreationService.CreaCatalogoParallel(imageCreationOptions, _results, UiUpdateEvent, token); Model.SpeedCounter = time; timer1.Enabled = false; } catch (OperationCanceledException operationCanceledException) { _logger.LogInformation("Operazione Cancellata"); } finally { Model.MainToken?.Dispose(); Model.MainToken = null; timer1.Tick -= Timer1OnTick; } Label10.Text = "Finito"; unlockUI(); } private int _currentAmount = 0; private int _previousAmount = 0; private void Timer1OnTick(object sender, EventArgs e) { _previousAmount = _currentAmount; _currentAmount = _results.Count; int diff = _currentAmount - _previousAmount; Model.SpeedCounter = $"{diff} f/m"; } } 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; } }