diff --git a/Photomator.sln b/Photomator.sln
new file mode 100644
index 0000000..f3adee2
--- /dev/null
+++ b/Photomator.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Photomator", "Photomator\Photomator.csproj", "{2D7D41C1-E5F9-49D8-AF2D-8549523EFF21}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {2D7D41C1-E5F9-49D8-AF2D-8549523EFF21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2D7D41C1-E5F9-49D8-AF2D-8549523EFF21}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2D7D41C1-E5F9-49D8-AF2D-8549523EFF21}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2D7D41C1-E5F9-49D8-AF2D-8549523EFF21}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/Photomator/AppInfo.cs b/Photomator/AppInfo.cs
new file mode 100644
index 0000000..929d589
--- /dev/null
+++ b/Photomator/AppInfo.cs
@@ -0,0 +1,19 @@
+public class AppInfo
+{
+ public static readonly string ApplicationName = "Photomator";
+ public static readonly string IconName = "de.cantorgymnasium.Photomator";
+ public static readonly string Version = ThisAssembly.Git.Tag;
+ public static readonly string ReleaseNotes = @"
+
0.0.1
+ Initial release
+
+ - single-file and folder conversions
+ - resolution and format setting
+ - Windows support
+
+ ";
+ public static readonly string Copyright = "© 2024 Denys Konovalov";
+ public static readonly string DeveloperName = "Denys Konovalov";
+ public static readonly string Website = "https://git.cantorgymnasium.de/gcg/Photomator";
+ public static readonly string IssueUrl = "https://git.cantorgymnasium.de/gcg/Photomator/issues";
+}
\ No newline at end of file
diff --git a/Photomator/Lib.cs b/Photomator/Lib.cs
new file mode 100644
index 0000000..beae214
--- /dev/null
+++ b/Photomator/Lib.cs
@@ -0,0 +1,69 @@
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats.Webp;
+using SixLabors.ImageSharp.Processing;
+
+namespace Photomator;
+
+public class Lib
+{
+ public static async Task Convert(string src, string dest, ConvertOptions options)
+ {
+ Image img = await Image.LoadAsync(src);
+ img.Mutate(i =>
+ {
+ i.AutoOrient();
+ Size currentSize = i.GetCurrentSize();
+ if (currentSize.Height > currentSize.Width)
+ {
+ i.Resize(options.Resolution, 0);
+ }
+ else
+ {
+ i.Resize(0, options.Resolution);
+ }
+ });
+ await img.SaveAsWebpAsync(dest, new WebpEncoder
+ {
+ FileFormat = options.Format
+ });
+ }
+
+ public static async Task ConvertList(string[] src, string dest, ConvertOptions options, Action setProgress)
+ {
+ double progress = 0;
+ List unconverted = [];
+ await Parallel.ForEachAsync(src, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, async (path, _) =>
+ {
+ string filename = path.Split(Path.DirectorySeparatorChar).Last();
+ string[] splitted = filename.Split('.');
+ splitted[^1] = "webp";
+ try
+ {
+ await Convert(path, dest + Path.DirectorySeparatorChar + string.Join(".", splitted), options);
+ }
+ catch (UnknownImageFormatException)
+ {
+ unconverted.Add(new ConvertError(filename, "Das Dateiformat wird nicht unterstützt."));
+ }
+ catch (Exception e)
+ {
+ unconverted.Add(new ConvertError(filename, e.Message));
+ }
+ progress += 1.0 / src.Length;
+ setProgress(progress);
+ });
+ return [.. unconverted];
+ }
+}
+
+public readonly struct ConvertOptions(string? resolution, uint format)
+{
+ public int Resolution { get; init; } = resolution != null ? Convert.ToInt32(resolution) : 1440;
+ public WebpFileFormatType Format { get; init; } = format == 1 ? WebpFileFormatType.Lossless : WebpFileFormatType.Lossy;
+}
+
+public readonly struct ConvertError(string file, string message)
+{
+ public string File { get; init; } = file;
+ public string Message { get; init; } = message;
+}
\ No newline at end of file
diff --git a/Photomator/Photomator.csproj b/Photomator/Photomator.csproj
new file mode 100644
index 0000000..73e7b64
--- /dev/null
+++ b/Photomator/Photomator.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
diff --git a/Photomator/Program.cs b/Photomator/Program.cs
new file mode 100644
index 0000000..8727993
--- /dev/null
+++ b/Photomator/Program.cs
@@ -0,0 +1,42 @@
+using System.Reflection;
+using Photomator.Views;
+
+namespace Photomator;
+
+public partial class Program
+{
+ public delegate void OpenCallback(nint application, nint[] files, int n_files, nint hint, nint data);
+
+ private readonly Adw.Application _application;
+
+ public static int Main() => new Program().Run();
+
+ public Program()
+ {
+ _application = Adw.Application.New("de.cantorgymnasium.Photomator", Gio.ApplicationFlags.FlagsNone);
+ Gtk.Window.SetDefaultIconName("de.cantorgymnasium.Photomator");
+ if (File.Exists(Path.GetFullPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!) + "/de.cantorgymnasium.Photomator.gresource"))
+ {
+ Gio.Functions.ResourcesRegister(Gio.Functions.ResourceLoad(Path.GetFullPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!) + "/de.cantorgymnasium.Photomator.gresource"));
+ }
+ else
+ {
+ var prefixes = new List {
+ Directory.GetParent(Directory.GetParent(Path.GetFullPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!))!.FullName)!.FullName,
+ Directory.GetParent(Path.GetFullPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!))!.FullName,
+ "/usr"
+ };
+ foreach (var prefix in prefixes)
+ {
+ if (File.Exists(prefix + "/share/de.cantorgymnasium.Photomator/de.cantorgymnasium.Photomator.gresource"))
+ {
+ Gio.Functions.ResourcesRegister(Gio.Functions.ResourceLoad(Path.GetFullPath(prefix + "/share/de.cantorgymnasium.Photomator/de.cantorgymnasium.Photomator.gresource")));
+ break;
+ }
+ }
+ }
+ _application.OnActivate += (_, _) => new MainWindow(_application).Start();
+ }
+
+ public int Run() => _application.RunWithSynchronizationContext(null);
+}
\ No newline at end of file
diff --git a/Photomator/Resources/de.cantorgymnasium.Photomator.gresource.xml b/Photomator/Resources/de.cantorgymnasium.Photomator.gresource.xml
new file mode 100644
index 0000000..fe5371d
--- /dev/null
+++ b/Photomator/Resources/de.cantorgymnasium.Photomator.gresource.xml
@@ -0,0 +1,6 @@
+
+
+
+ de.cantorgymnasium.Photomator.svg
+
+
\ No newline at end of file
diff --git a/Photomator/Resources/de.cantorgymnasium.Photomator.svg b/Photomator/Resources/de.cantorgymnasium.Photomator.svg
new file mode 100644
index 0000000..a6e85aa
--- /dev/null
+++ b/Photomator/Resources/de.cantorgymnasium.Photomator.svg
@@ -0,0 +1,131 @@
+
+
diff --git a/Photomator/Views/MainWindow.cs b/Photomator/Views/MainWindow.cs
new file mode 100644
index 0000000..6a36c76
--- /dev/null
+++ b/Photomator/Views/MainWindow.cs
@@ -0,0 +1,374 @@
+using Gtk;
+using Adw;
+using System.Text;
+
+namespace Photomator.Views;
+
+public partial class MainWindow : Adw.ApplicationWindow
+{
+ private readonly Adw.Application _application;
+
+ public MainWindow(Adw.Application application) : base()
+ {
+ _application = application;
+
+ SetTitle(AppInfo.ApplicationName);
+ SetIconName(AppInfo.IconName);
+
+ PreferencesPage layoutSingle = InitPageSingle();
+ PreferencesPage layoutFolder = InitPageFolder();
+
+ ViewStack viewStack = ViewStack.New();
+ viewStack.Add(layoutSingle);
+ ViewStackPage pageSingle = viewStack.GetPage(layoutSingle);
+ pageSingle.SetTitle("Einzelne Datei");
+ pageSingle.SetIconName("image-x-generic-symbolic");
+ viewStack.Add(layoutFolder);
+ ViewStackPage pageFolder = viewStack.GetPage(layoutFolder);
+ pageFolder.SetTitle("Ordner");
+ pageFolder.SetIconName("folder-symbolic");
+
+ ViewSwitcher viewSwitcher = ViewSwitcher.New();
+ viewSwitcher.SetStack(viewStack);
+ viewSwitcher.SetPolicy(ViewSwitcherPolicy.Wide);
+
+ SetDefaultSize(600, 800);
+
+ Adw.AboutDialog aboutDialog = InitAboutDialog();
+
+ Adw.HeaderBar headerBar = Adw.HeaderBar.New();
+ Button btnAbout = Button.NewFromIconName("help-about-symbolic");
+ btnAbout.OnClicked += (_, _) => aboutDialog.Present(this);
+ headerBar.SetTitleWidget(viewSwitcher);
+ headerBar.PackEnd(btnAbout);
+
+ ToolbarView view = ToolbarView.New();
+ view.AddTopBar(headerBar);
+
+ view.SetContent(viewStack);
+
+ SetContent(view);
+ }
+
+ public void Start()
+ {
+ _application.AddWindow(this);
+ Present();
+ }
+
+ public PreferencesPage InitPageSingle()
+ {
+ PreferencesPage page = PreferencesPage.New();
+
+ PreferencesGroup pgSelect = PreferencesGroup.New();
+ pgSelect.SetTitle("Bild");
+ pgSelect.SetDescription("Als Eingabeformate werden BMP, GIF, JPEG, PBM, PNG, TIFF, TGA und WebP unterstützt.");
+ EntryRow erSelect = EntryRow.New();
+ erSelect.SetTitle("Zu konvertierendes Bild");
+ erSelect.SetEditable(false);
+
+ PreferencesGroup pgOptions = PreferencesGroup.New();
+ pgOptions.SetTitle("Optionen");
+ pgOptions.SetDescription("Diese Parameter beeinflussen die Qualität und Größe der Bilder.");
+
+ ComboRow crRes = ComboRow.New();
+ crRes.SetTitle("Auflösung");
+ crRes.SetSubtitle("Länge der kurzen Seite in Pixel");
+ StringList resOptions = StringList.New(["720", "1080", "1440", "2160"]);
+ crRes.SetModel(resOptions);
+ crRes.SetSelected(2);
+ pgOptions.Add(crRes);
+
+ ComboRow crFormat = ComboRow.New();
+ crFormat.SetTitle("Konvertierungsart");
+ crFormat.SetModel(StringList.New(["Verlustbehaftet", "Verlustfrei"]));
+ crFormat.SetSelected(0);
+ pgOptions.Add(crFormat);
+
+ PreferencesGroup pgBtn = PreferencesGroup.New();
+ pgBtn.SetValign(Align.Center);
+ pgBtn.SetHalign(Align.Center);
+ Button btnConvert = Button.NewWithLabel("Konvertieren");
+ btnConvert.OnClicked += async (_, _) =>
+ {
+ FileDialog fileDialog = FileDialog.New();
+ Gio.ListStore listStore = Gio.ListStore.New(FileFilter.GetGType());
+ FileFilter filter = FileFilter.New();
+ filter.SetName("WebP (*.webp)");
+ filter.AddPattern("*.webp");
+ filter.AddPattern("*.WEBP");
+ listStore.Append(filter);
+ fileDialog.SetFilters(listStore);
+ Gio.File? file;
+ try
+ {
+ file = await fileDialog.SaveAsync(this);
+ }
+ catch
+ {
+ file = null;
+ }
+
+ if (file != null && file.GetPath() != null)
+ {
+ ConvertOptions options = new(resOptions.GetString(crRes.GetSelected()), crFormat.GetSelected());
+ await Lib.Convert(erSelect.GetText(), file.GetPath()!, options);
+ }
+ else
+ InitInfoAlert("Kein Ziel ausgewählt", "Es wurde keine Zieldatei ausgewählt. Konvertierung wird abgebrochen.");
+ };
+ btnConvert.SetCssClasses(["pill", "suggested-action", "long"]);
+ btnConvert.SetSensitive(false);
+ pgBtn.Add(btnConvert);
+
+ Button btnSelect = Button.New();
+ btnSelect.SetValign(Align.Center);
+ btnSelect.AddCssClass("flat");
+ btnSelect.SetIconName("document-open-symbolic");
+ btnSelect.OnClicked += async (_, _) =>
+ {
+ FileDialog fileDialog = FileDialog.New();
+ Gio.ListStore listStore = Gio.ListStore.New(FileFilter.GetGType());
+ FileFilter filterAll = FileFilter.New();
+ filterAll.SetName("Alle unterstützten Bildformate");
+ string[][] formats = [["bmp", "dib"], ["gif"], ["jpeg", "jpg", "jpe", "jfif"], ["pbm"], ["png"], ["tiff", "tif"], ["tga", "bpx", "icb", "pix"], ["webp"]];
+ foreach (string[] format in formats)
+ {
+ FileFilter filter = FileFilter.New();
+ StringBuilder name = new($"{format[0].ToUpper()} (");
+ for (int i = 0; i < format.Length; i++)
+ {
+ name.Append($"*.{format[i]}");
+ if (i != format.Length - 1)
+ name.Append(", ");
+ filter.AddPattern($"*.{format[i]}");
+ filterAll.AddPattern($"*.{format[i]}");
+ filter.AddPattern($"*.{format[i].ToUpper()}");
+ filterAll.AddPattern($"*.{format[i].ToUpper()}");
+ }
+ name.Append(')');
+ filter.SetName(name.ToString());
+ listStore.Append(filter);
+ }
+ listStore.Insert(0, filterAll);
+ fileDialog.SetFilters(listStore);
+ Gio.File? file;
+ try
+ {
+ file = await fileDialog.OpenAsync(this);
+ }
+ catch
+ {
+ file = null;
+ }
+ if (file != null && file.GetPath() != null)
+ {
+ erSelect.SetText(file.GetPath()!);
+ btnConvert.SetSensitive(true);
+ }
+ else if (erSelect.GetText() == "")
+ btnConvert.SetSensitive(false);
+ };
+
+ erSelect.AddSuffix(btnSelect);
+ pgSelect.Add(erSelect);
+
+ page.Add(pgSelect);
+ page.Add(pgOptions);
+ page.Add(pgBtn);
+
+ return page;
+ }
+
+ public PreferencesPage InitPageFolder()
+ {
+ EntryRow erSource = EntryRow.New();
+ erSource.SetTitle("Quellordner");
+ erSource.SetEditable(false);
+
+ EntryRow erDest = EntryRow.New();
+ erDest.SetTitle("Zielordner");
+ erDest.SetEditable(false);
+
+ PreferencesGroup pgOptions = PreferencesGroup.New();
+ pgOptions.SetTitle("Optionen");
+ pgOptions.SetDescription("Diese Parameter beeinflussen die Qualität und Größe der Bilder.");
+
+ ComboRow crRes = ComboRow.New();
+ crRes.SetTitle("Auflösung");
+ crRes.SetSubtitle("Länge der kurzen Seite in Pixel");
+ StringList resOptions = StringList.New(["720", "1080", "1440", "2160"]);
+ crRes.SetModel(resOptions);
+ crRes.SetSelected(2);
+ pgOptions.Add(crRes);
+
+ ComboRow crFormat = ComboRow.New();
+ crFormat.SetTitle("Konvertierungsart");
+ crFormat.SetModel(StringList.New(["Verlustbehaftet", "Verlustfrei"]));
+ crFormat.SetSelected(0);
+ pgOptions.Add(crFormat);
+
+ PreferencesGroup pgBtn = PreferencesGroup.New();
+ pgBtn.SetValign(Align.Center);
+ pgBtn.SetHalign(Align.Center);
+ Button btnConvert = Button.NewWithLabel("Konvertieren");
+ btnConvert.OnClicked += async (_, _) =>
+ {
+ if (erSource.GetText() != "" && erDest.GetText() != "")
+ {
+ string[] filesSource;
+ if (Directory.Exists(erSource.GetText()))
+ filesSource = Directory.GetFiles(erSource.GetText());
+ else
+ {
+ InitInfoAlert("Quellordner existiert nicht", "Der ausgewählte Quellordner ist nicht im Dateisystem vorhanden. Die Konvertierung wurde abgebrochen.");
+ return;
+ }
+ if (filesSource.Length == 0)
+ {
+ InitInfoAlert("Quellordner ist leer", "Im ausgewählten Quellordner befinden sich keine Dateien. Die Konvertierung wurde abgebrochen.");
+ return;
+ }
+ string dest = erDest.GetText();
+ if (!Directory.Exists(dest))
+ Directory.CreateDirectory(dest);
+ StatusDialog statusDialog = new(_application, this);
+ statusDialog.Start();
+ try
+ {
+ ConvertOptions options = new ConvertOptions(resOptions.GetString(crRes.GetSelected()), crFormat.GetSelected());
+ ConvertError[] convertErrors = await Lib.ConvertList(filesSource, dest, options, statusDialog.SetProgress);
+ if (convertErrors.Length > 0)
+ statusDialog.Partial(filesSource.Length, dest, convertErrors);
+ else
+ statusDialog.Success(filesSource.Length, dest);
+ }
+ catch (Exception e)
+ {
+ statusDialog.Error(e.Message);
+ }
+ }
+ };
+ btnConvert.SetCssClasses(["pill", "suggested-action", "long"]);
+ btnConvert.SetSensitive(false);
+ pgBtn.Add(btnConvert);
+
+ void checkButtonState()
+ {
+ if (erSource.GetText() != "" && erDest.GetText() != "") btnConvert.SetSensitive(true);
+ else btnConvert.SetSensitive(false);
+ }
+
+ Button btnSource = Button.New();
+ btnSource.SetValign(Align.Center);
+ btnSource.AddCssClass("flat");
+ btnSource.SetIconName("document-open-symbolic");
+ btnSource.OnClicked += async (_, _) =>
+ {
+ FileDialog fileDialog = FileDialog.New();
+ Gio.File? file;
+ try
+ {
+ file = await fileDialog.SelectFolderAsync(this);
+ }
+ catch
+ {
+ file = null;
+ }
+ if (file != null && file.GetPath() != null)
+ erSource.SetText(file.GetPath()!);
+ checkButtonState();
+ };
+ erSource.AddSuffix(btnSource);
+
+ Button btnDest = Button.New();
+ btnDest.SetValign(Align.Center);
+ btnDest.AddCssClass("flat");
+ btnDest.SetIconName("document-open-symbolic");
+ btnDest.OnClicked += async (_, _) =>
+ {
+ FileDialog fileDialog = FileDialog.New();
+ Gio.File? file;
+ try
+ {
+ file = await fileDialog.SelectFolderAsync(this);
+ }
+ catch
+ {
+ file = null;
+ }
+ if (file != null && file.GetPath() != null)
+ erDest.SetText(file.GetPath()!);
+ checkButtonState();
+ };
+ erDest.AddSuffix(btnDest);
+
+ PreferencesPage page = PreferencesPage.New();
+
+ PreferencesGroup pgFolders = PreferencesGroup.New();
+ pgFolders.SetTitle("Ordner");
+ pgFolders.SetDescription("Wähle einen Quell- und einen Zielordner. Es werden alle Dateien im Ordner konvertiert, jedoch keine Unterordner.");
+ pgFolders.Add(erSource);
+ pgFolders.Add(erDest);
+ page.Add(pgFolders);
+ page.Add(pgOptions);
+ page.Add(pgBtn);
+
+ return page;
+ }
+
+ public void InitInfoAlert(string title, string body)
+ {
+ Adw.AlertDialog alert = Adw.AlertDialog.New(title, body);
+ alert.AddResponse("ok", "OK");
+ alert.SetResponseAppearance("ok", ResponseAppearance.Suggested);
+ alert.SetDefaultResponse("ok");
+ alert.SetCloseResponse("ok");
+ alert.Present(this);
+ }
+
+ public bool InitDestructiveAlert(string title, string body, string resp)
+ {
+ Adw.AlertDialog alert = Adw.AlertDialog.New(title, body);
+ alert.AddResponse("cancel", "Abbrechen");
+ alert.AddResponse(resp.ToLower(), resp);
+ alert.SetResponseAppearance(resp.ToLower(), ResponseAppearance.Destructive);
+ alert.SetDefaultResponse("cancel");
+ alert.SetCloseResponse("cancel");
+
+ bool running = true;
+ bool response = false;
+ alert.OnResponse += (Adw.AlertDialog _, Adw.AlertDialog.ResponseSignalArgs args) =>
+ {
+ if (args.Response.Equals(resp, StringComparison.CurrentCultureIgnoreCase))
+ {
+ response = true;
+ }
+ running = false;
+ };
+
+ alert.Present(this);
+
+ while (running == true)
+ {
+ }
+
+ return response;
+ }
+
+ public Adw.AboutDialog InitAboutDialog()
+ {
+ Adw.AboutDialog aboutDialog = Adw.AboutDialog.New();
+ aboutDialog.SetApplicationName(AppInfo.ApplicationName);
+ aboutDialog.SetDeveloperName(AppInfo.DeveloperName);
+ aboutDialog.SetApplicationIcon(AppInfo.IconName);
+ aboutDialog.SetCopyright(AppInfo.Copyright);
+ aboutDialog.SetIssueUrl(AppInfo.IssueUrl);
+ aboutDialog.SetWebsite(AppInfo.Website);
+ aboutDialog.SetReleaseNotes(AppInfo.ReleaseNotes);
+ aboutDialog.SetVersion(AppInfo.Version);
+ aboutDialog.SetLicenseType(License.MitX11);
+
+ return aboutDialog;
+ }
+}
\ No newline at end of file
diff --git a/Photomator/Views/StatusDialog.cs b/Photomator/Views/StatusDialog.cs
new file mode 100644
index 0000000..fd9ff49
--- /dev/null
+++ b/Photomator/Views/StatusDialog.cs
@@ -0,0 +1,215 @@
+using Gtk;
+using Adw;
+using System.Text;
+
+namespace Photomator.Views;
+
+public partial class StatusDialog : Adw.Window
+{
+ private readonly Adw.Application _application;
+ private readonly Adw.ApplicationWindow _mainWindow;
+ private readonly ViewStack _stack;
+ private readonly ProgressBar _progress;
+
+ public StatusDialog(Adw.Application application, Adw.ApplicationWindow parent) : base()
+ {
+ _application = application;
+ _mainWindow = parent;
+ _progress = InitProgressBar();
+
+ SetModal(true);
+ SetTransientFor(parent);
+ SetDeletable(true);
+ SetDefaultSize(400, 500);
+
+ _stack = ViewStack.New();
+ _stack.SetVexpand(true);
+
+ StatusPage pageStatus = InitPageProgress();
+ _stack.AddNamed(pageStatus, "page-status");
+
+ SetContent(_stack);
+ }
+
+ public void Start()
+ {
+ _application.AddWindow(this);
+ Present();
+ }
+
+ private StatusPage InitPageProgress()
+ {
+ StatusPage pageStatus = StatusPage.New();
+ pageStatus.SetTitle("Konvertierung wird ausgeführt...");
+ pageStatus.SetDescription("Das kann eine Weile dauern.");
+ pageStatus.SetChild(_progress);
+
+ return pageStatus;
+ }
+
+ private ProgressBar InitProgressBar()
+ {
+ ProgressBar progressBar = ProgressBar.New();
+ progressBar.WidthRequest = 300;
+ progressBar.SetHalign(Align.Center);
+ progressBar.SetMarginTop(10);
+ progressBar.SetMarginBottom(12);
+ progressBar.SetShowText(true);
+ progressBar.AddCssClass("installer");
+ progressBar.SetFraction(0);
+ progressBar.SetText("");
+
+ return progressBar;
+ }
+
+ public void SetProgress(double progress)
+ {
+ _progress.SetFraction(progress);
+ _progress.SetText(string.Format("{0:p0}", progress));
+ }
+
+ private StatusPage InitPageSuccess(int number, string dest)
+ {
+ StatusPage pageStatus = StatusPage.New();
+ pageStatus.SetTitle("Die Konvertierung wurde erfolgreich abgeschlossen");
+ pageStatus.SetDescription($"{number} Dateien wurden erfolgreich konvertiert.");
+ pageStatus.SetIconName("selection-mode-symbolic");
+
+ Button btnOpen = Button.NewWithLabel("Ordner öffnen");
+ btnOpen.OnClicked += async (_, _) =>
+ {
+ FileLauncher fileLauncher = FileLauncher.New(Gio.FileHelper.NewForPath(dest));
+ await fileLauncher.LaunchAsync(_mainWindow);
+ Close();
+ };
+ btnOpen.SetCssClasses(["pill", "suggested-action"]);
+ btnOpen.SetHalign(Align.Center);
+
+ pageStatus.SetChild(btnOpen);
+
+ return pageStatus;
+ }
+
+ public void Success(int number, string dest)
+ {
+ StatusPage pageSuccess = InitPageSuccess(number, dest);
+ _stack.AddNamed(pageSuccess, "page-success");
+ _stack.SetVisibleChildName("page-success");
+ }
+
+ private StatusPage InitPagePartial(int number, string dest, ConvertError[] convertErrors)
+ {
+ StatusPage pageStatus = StatusPage.New();
+ pageStatus.SetTitle("Bei der Konvertierung sind Fehler aufgetreten");
+ pageStatus.SetDescription($"{number - convertErrors.Length} von {number} Dateien wurden erfolgreich konvertiert.");
+ pageStatus.SetIconName("dialog-warning-symbolic");
+
+ Box box = Box.New(Orientation.Vertical, 36);
+ box.SetHalign(Align.Center);
+
+ if (convertErrors.Length < number)
+ {
+ Button btnOpen = Button.NewWithLabel("Ordner öffnen");
+ btnOpen.OnClicked += async (_, _) =>
+ {
+ FileLauncher fileLauncher = FileLauncher.New(Gio.FileHelper.NewForPath(dest));
+ await fileLauncher.LaunchAsync(_mainWindow);
+ Close();
+ };
+ btnOpen.SetCssClasses(["pill", "suggested-action"]);
+ btnOpen.SetHalign(Align.Center);
+
+ box.Append(btnOpen);
+ }
+
+ ScrolledWindow scrolled = ScrolledWindow.New();
+ scrolled.SetMinContentWidth(328);
+ scrolled.SetMinContentHeight(200);
+ scrolled.SetOverflow(Overflow.Hidden);
+
+ StringBuilder sb = new StringBuilder();
+ foreach (ConvertError err in convertErrors)
+ {
+ sb.AppendLine($"{err.File}: {err.Message}");
+ }
+ Label label = Label.New(sb.ToString());
+ label.SetHexpand(true);
+ label.SetValign(Align.Fill);
+ label.SetWrap(false);
+ label.SetSelectable(true);
+ label.SetXalign(0);
+ label.SetYalign(0);
+ label.SetMarginStart(10);
+ label.SetMarginEnd(10);
+ label.SetMarginTop(5);
+ label.SetMarginBottom(5);
+ label.SetCssClasses(["monospace", "terminal"]);
+
+ scrolled.SetChild(label);
+ scrolled.AddCssClass("card");
+
+ box.Append(scrolled);
+
+ pageStatus.SetChild(box);
+
+ return pageStatus;
+ }
+
+ public void Partial(int number, string dest, ConvertError[] convertErrors)
+ {
+ StatusPage pagePartial = InitPagePartial(number, dest, convertErrors);
+ _stack.AddNamed(pagePartial, "page-partial");
+ _stack.SetVisibleChildName("page-partial");
+ }
+
+ private StatusPage InitPageError(string error)
+ {
+ StatusPage pageStatus = StatusPage.New();
+ pageStatus.SetTitle("Konvertierung fehlgeschlagen");
+ pageStatus.SetDescription("Es ist ein Fehler aufgetreten.");
+ pageStatus.SetIconName("dialog-error-symbolic");
+
+ Box box = Box.New(Orientation.Vertical, 50);
+ box.SetHalign(Align.Center);
+
+ ScrolledWindow scrolled = ScrolledWindow.New();
+ scrolled.SetMinContentWidth(328);
+ scrolled.SetMinContentHeight(200);
+ scrolled.SetOverflow(Overflow.Hidden);
+
+ Label label = Label.New(error);
+ label.SetHexpand(true);
+ label.SetValign(Align.Fill);
+ label.SetWrap(true);
+ label.SetSelectable(true);
+ label.SetXalign(0);
+ label.SetYalign(0);
+ label.SetMarginStart(10);
+ label.SetMarginEnd(10);
+ label.SetMarginTop(5);
+ label.SetMarginBottom(5);
+ label.SetCssClasses(["monospace", "terminal"]);
+
+ scrolled.SetChild(label);
+ scrolled.AddCssClass("card");
+
+ Button button = Button.NewWithLabel("Schließen");
+ button.OnClicked += (_, _) => Close();
+ button.SetCssClasses(["pill"]);
+ button.SetHalign(Align.Center);
+
+ box.Append(scrolled);
+ box.Append(button);
+
+ pageStatus.SetChild(box);
+
+ return pageStatus;
+ }
+
+ public void Error(string error)
+ {
+ StatusPage pageError = InitPageError(error);
+ _stack.AddNamed(pageError, "page-error");
+ _stack.SetVisibleChildName("page-error");
+ }
+}
\ No newline at end of file