Initial .NET version

This commit is contained in:
2024-09-08 22:37:06 +02:00
parent 7ee6a69f37
commit 5f1c8d8a55
9 changed files with 902 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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");
}
}