diff --git a/MagiConvert/MainWindow.Designer.cs b/MagiConvert/MainWindow.Designer.cs index 7627a51..514e0d6 100644 --- a/MagiConvert/MainWindow.Designer.cs +++ b/MagiConvert/MainWindow.Designer.cs @@ -26,11 +26,19 @@ protected override void Dispose(bool disposing) /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// - private void InitializeComponent() + private void InitializeComponent() { this.buttonBrowse = new System.Windows.Forms.Button(); this.InputPath = new System.Windows.Forms.TextBox(); this.openFileDialog = new System.Windows.Forms.OpenFileDialog(); + this.btnSelectInputFolder = new System.Windows.Forms.Button(); + this.txtInputFolder = new System.Windows.Forms.TextBox(); + this.btnSelectOutputFolder = new System.Windows.Forms.Button(); + this.txtOutputFolder = new System.Windows.Forms.TextBox(); + this.btnConvertFolder = new System.Windows.Forms.Button(); + this.progressBarBatch = new System.Windows.Forms.ProgressBar(); + this.lblBatchStatus = new System.Windows.Forms.Label(); + this.txtBatchLog = new System.Windows.Forms.TextBox(); this.radioButtonPNG = new System.Windows.Forms.RadioButton(); this.radioButtonICO = new System.Windows.Forms.RadioButton(); this.checkBoxTransparent = new System.Windows.Forms.CheckBox(); @@ -75,6 +83,84 @@ private void InitializeComponent() // this.openFileDialog.FileName = "openFileDialog"; // + // btnSelectInputFolder + // + this.btnSelectInputFolder.Font = new System.Drawing.Font("Century", 10.2F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.btnSelectInputFolder.Location = new System.Drawing.Point(12, 262); + this.btnSelectInputFolder.Name = "btnSelectInputFolder"; + this.btnSelectInputFolder.Size = new System.Drawing.Size(149, 30); + this.btnSelectInputFolder.TabIndex = 9; + this.btnSelectInputFolder.Text = "Input Folder"; + this.btnSelectInputFolder.UseVisualStyleBackColor = true; + this.btnSelectInputFolder.Click += new System.EventHandler(this.BtnSelectInputFolder_Click); + // + // txtInputFolder + // + this.txtInputFolder.Font = new System.Drawing.Font("Microsoft Sans Serif", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.txtInputFolder.Location = new System.Drawing.Point(165, 264); + this.txtInputFolder.Name = "txtInputFolder"; + this.txtInputFolder.ReadOnly = true; + this.txtInputFolder.Size = new System.Drawing.Size(578, 28); + this.txtInputFolder.TabIndex = 10; + // + // btnSelectOutputFolder + // + this.btnSelectOutputFolder.Font = new System.Drawing.Font("Century", 10.2F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.btnSelectOutputFolder.Location = new System.Drawing.Point(12, 298); + this.btnSelectOutputFolder.Name = "btnSelectOutputFolder"; + this.btnSelectOutputFolder.Size = new System.Drawing.Size(149, 30); + this.btnSelectOutputFolder.TabIndex = 11; + this.btnSelectOutputFolder.Text = "Output Folder"; + this.btnSelectOutputFolder.UseVisualStyleBackColor = true; + this.btnSelectOutputFolder.Click += new System.EventHandler(this.BtnSelectOutputFolder_Click); + // + // txtOutputFolder + // + this.txtOutputFolder.Font = new System.Drawing.Font("Microsoft Sans Serif", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.txtOutputFolder.Location = new System.Drawing.Point(165, 300); + this.txtOutputFolder.Name = "txtOutputFolder"; + this.txtOutputFolder.ReadOnly = true; + this.txtOutputFolder.Size = new System.Drawing.Size(578, 28); + this.txtOutputFolder.TabIndex = 12; + // + // btnConvertFolder + // + this.btnConvertFolder.Font = new System.Drawing.Font("Century", 10.2F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.btnConvertFolder.Location = new System.Drawing.Point(12, 334); + this.btnConvertFolder.Name = "btnConvertFolder"; + this.btnConvertFolder.Size = new System.Drawing.Size(149, 30); + this.btnConvertFolder.TabIndex = 13; + this.btnConvertFolder.Text = "Convert Folder"; + this.btnConvertFolder.UseVisualStyleBackColor = true; + this.btnConvertFolder.Click += new System.EventHandler(this.BtnConvertFolder_Click); + // + // progressBarBatch + // + this.progressBarBatch.Location = new System.Drawing.Point(165, 334); + this.progressBarBatch.Name = "progressBarBatch"; + this.progressBarBatch.Size = new System.Drawing.Size(578, 23); + this.progressBarBatch.TabIndex = 14; + // + // lblBatchStatus + // + this.lblBatchStatus.AutoSize = true; + this.lblBatchStatus.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblBatchStatus.Location = new System.Drawing.Point(165, 360); + this.lblBatchStatus.Name = "lblBatchStatus"; + this.lblBatchStatus.Size = new System.Drawing.Size(0, 18); + this.lblBatchStatus.TabIndex = 15; + // + // txtBatchLog + // + this.txtBatchLog.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.txtBatchLog.Location = new System.Drawing.Point(12, 385); + this.txtBatchLog.Multiline = true; + this.txtBatchLog.Name = "txtBatchLog"; + this.txtBatchLog.ReadOnly = true; + this.txtBatchLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtBatchLog.Size = new System.Drawing.Size(731, 120); + this.txtBatchLog.TabIndex = 16; + // // radioButtonPNG // this.radioButtonPNG.AutoSize = true; @@ -287,7 +373,15 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(755, 262); + this.ClientSize = new System.Drawing.Size(755, 517); + this.Controls.Add(this.txtBatchLog); + this.Controls.Add(this.lblBatchStatus); + this.Controls.Add(this.progressBarBatch); + this.Controls.Add(this.btnConvertFolder); + this.Controls.Add(this.txtOutputFolder); + this.Controls.Add(this.btnSelectOutputFolder); + this.Controls.Add(this.txtInputFolder); + this.Controls.Add(this.btnSelectInputFolder); this.Controls.Add(this.labelDone); this.Controls.Add(this.OutputPath); this.Controls.Add(this.buttonConvert); @@ -331,6 +425,13 @@ private void InitializeComponent() private System.Windows.Forms.Button buttonConvert; private System.Windows.Forms.Label OutputPath; private System.Windows.Forms.Label labelDone; + private System.Windows.Forms.Button btnSelectInputFolder; + private System.Windows.Forms.TextBox txtInputFolder; + private System.Windows.Forms.Button btnSelectOutputFolder; + private System.Windows.Forms.TextBox txtOutputFolder; + private System.Windows.Forms.Button btnConvertFolder; + private System.Windows.Forms.ProgressBar progressBarBatch; + private System.Windows.Forms.Label lblBatchStatus; + private System.Windows.Forms.TextBox txtBatchLog; } } - diff --git a/MagiConvert/MainWindow.cs b/MagiConvert/MainWindow.cs index 370789e..306bfe0 100644 --- a/MagiConvert/MainWindow.cs +++ b/MagiConvert/MainWindow.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Data; using System.Drawing; using System.IO; -using System.Linq; -using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Forms; using CEngine; @@ -13,9 +10,13 @@ namespace MagiConvert { public partial class MainWindow : Form { + private readonly HashSet supportedExtensions = new HashSet(StringComparer.OrdinalIgnoreCase); + public MainWindow() { InitializeComponent(); + InitializeSupportedExtensions(); + btnConvertFolder.Enabled = false; } private void ButtonBrowse_Click(object sender, EventArgs e) @@ -138,5 +139,249 @@ private void GetFormat() outputString += format; OutputPath.Text = outputString; } + + private void InitializeSupportedExtensions() + { + if (!File.Exists("Filter.flt")) + return; + + string filterText = File.ReadAllText("Filter.flt"); + foreach (Match match in Regex.Matches(filterText, @"\*\.([a-zA-Z0-9]+)|\*([a-zA-Z0-9]+)")) + { + string extension = match.Groups[1].Success ? match.Groups[1].Value : match.Groups[2].Value; + if (string.IsNullOrWhiteSpace(extension)) + continue; + supportedExtensions.Add("." + extension.TrimStart('.')); + } + } + + private void BtnSelectInputFolder_Click(object sender, EventArgs e) + { + if (TrySelectFolder(txtInputFolder)) + { + var validation = ValidateInputFolder(txtInputFolder.Text); + if (!validation.ok) + MessageBox.Show(validation.message, "Invalid Input Folder", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + + UpdateBatchControls(); + } + + private void BtnSelectOutputFolder_Click(object sender, EventArgs e) + { + if (TrySelectFolder(txtOutputFolder)) + { + var validation = ValidateOutputFolder(txtOutputFolder.Text); + if (!validation.ok) + MessageBox.Show(validation.message, "Invalid Output Folder", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + + UpdateBatchControls(); + } + + private async void BtnConvertFolder_Click(object sender, EventArgs e) + { + txtBatchLog.Clear(); + lblBatchStatus.Text = ""; + progressBarBatch.Value = 0; + + var inputValidation = ValidateInputFolder(txtInputFolder.Text); + if (!inputValidation.ok) + { + MessageBox.Show(inputValidation.message, "Invalid Input Folder", MessageBoxButtons.OK, MessageBoxIcon.Warning); + UpdateBatchControls(); + return; + } + + var outputValidation = ValidateOutputFolder(txtOutputFolder.Text); + if (!outputValidation.ok) + { + MessageBox.Show(outputValidation.message, "Invalid Output Folder", MessageBoxButtons.OK, MessageBoxIcon.Warning); + UpdateBatchControls(); + return; + } + + SetBatchUiEnabled(false); + var progress = new Progress(info => + { + progressBarBatch.Maximum = info.Total; + progressBarBatch.Value = info.Current; + lblBatchStatus.Text = $"{info.Current}/{info.Total} - {info.FileName}"; + }); + + BatchResult result = await Task.Run(() => ConvertBatch(txtInputFolder.Text, txtOutputFolder.Text, progress)); + + progressBarBatch.Value = progressBarBatch.Maximum; + lblBatchStatus.Text = $"Completed {result.SuccessCount}/{result.TotalCount}"; + if (result.Errors.Count > 0) + { + txtBatchLog.Text = string.Join(Environment.NewLine, result.Errors); + } + + string summary = $"Completed batch conversion.\nSuccess: {result.SuccessCount}\nFailed: {result.Errors.Count}"; + if (result.Errors.Count > 0) + { + int displayCount = Math.Min(result.Errors.Count, 5); + summary += "\n\nErrors:\n" + string.Join("\n", result.Errors.GetRange(0, displayCount)); + } + + MessageBox.Show(summary, "Batch Conversion", MessageBoxButtons.OK, result.Errors.Count == 0 ? MessageBoxIcon.Information : MessageBoxIcon.Warning); + SetBatchUiEnabled(true); + UpdateBatchControls(); + } + + private void UpdateBatchControls() + { + var inputValidation = ValidateInputFolder(txtInputFolder.Text); + var outputValidation = ValidateOutputFolder(txtOutputFolder.Text); + btnConvertFolder.Enabled = inputValidation.ok && outputValidation.ok; + } + + private void SetBatchUiEnabled(bool enabled) + { + btnSelectInputFolder.Enabled = enabled; + btnSelectOutputFolder.Enabled = enabled; + btnConvertFolder.Enabled = enabled; + buttonBrowse.Enabled = enabled; + buttonConvert.Enabled = enabled; + groupBoxBMP.Enabled = enabled; + } + + private bool TrySelectFolder(TextBox targetTextBox) + { + using (var folderDialog = new FolderBrowserDialog()) + { + folderDialog.SelectedPath = Directory.Exists(targetTextBox.Text) ? targetTextBox.Text : ""; + folderDialog.ShowNewFolderButton = true; + if (folderDialog.ShowDialog() == DialogResult.OK) + { + targetTextBox.Text = folderDialog.SelectedPath; + return true; + } + } + + return false; + } + + private (bool ok, string message) ValidateInputFolder(string path) + { + if (string.IsNullOrWhiteSpace(path)) + return (false, "Please select an input folder."); + + if (!Directory.Exists(path)) + return (false, "Input folder does not exist."); + + List images = GetInputImages(path); + if (images.Count == 0) + return (false, "Input folder does not contain any supported image files."); + + return (true, string.Empty); + } + + private (bool ok, string message) ValidateOutputFolder(string path) + { + if (string.IsNullOrWhiteSpace(path)) + return (false, "Please select an output folder."); + + try + { + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + + string testFile = Path.Combine(path, Path.GetRandomFileName()); + using (File.Create(testFile)) + { + } + File.Delete(testFile); + } + catch (Exception ex) + { + return (false, $"Output folder is not writable. {ex.Message}"); + } + + return (true, string.Empty); + } + + private List GetInputImages(string inputDir) + { + var results = new List(); + if (supportedExtensions.Count == 0) + return results; + + foreach (string file in Directory.EnumerateFiles(inputDir)) + { + string extension = Path.GetExtension(file); + if (supportedExtensions.Contains(extension)) + results.Add(file); + } + + return results; + } + + private string GetUniqueOutputPath(string outputDir, string baseName, string ext) + { + string candidate = Path.Combine(outputDir, baseName + ext); + int counter = 1; + while (File.Exists(candidate)) + { + candidate = Path.Combine(outputDir, $"{baseName}_{counter}{ext}"); + counter++; + } + + return candidate; + } + + private void ConvertSingleImage(string inputPath, string outputPath) + { + ConvertEngine.Convert(inputPath, outputPath, format, checkBoxTransparent.Checked); + } + + private BatchResult ConvertBatch(string inputDir, string outputDir, IProgress progress) + { + List images = GetInputImages(inputDir); + var result = new BatchResult { TotalCount = images.Count }; + + for (int index = 0; index < images.Count; index++) + { + string inputPath = images[index]; + string baseName = Path.GetFileNameWithoutExtension(inputPath); + string outputPath = GetUniqueOutputPath(outputDir, baseName, format); + + try + { + ConvertSingleImage(inputPath, outputPath); + result.SuccessCount++; + } + catch (Exception ex) + { + result.Errors.Add($"{Path.GetFileName(inputPath)}: {ex.Message}"); + } + + progress?.Report(new ProgressInfo(index + 1, images.Count, Path.GetFileName(inputPath))); + } + + return result; + } + + private sealed class BatchResult + { + public int TotalCount { get; set; } + public int SuccessCount { get; set; } + public List Errors { get; } = new List(); + } + + private readonly struct ProgressInfo + { + public ProgressInfo(int current, int total, string fileName) + { + Current = current; + Total = total; + FileName = fileName; + } + + public int Current { get; } + public int Total { get; } + public string FileName { get; } + } } }