diff --git a/App.axaml b/App.axaml
index ad778c3..3046fdc 100644
--- a/App.axaml
+++ b/App.axaml
@@ -7,4 +7,8 @@
+
+ 10
+ 1000
+
\ No newline at end of file
diff --git a/Backup.cs b/Backup.cs
new file mode 100644
index 0000000..4267d1d
--- /dev/null
+++ b/Backup.cs
@@ -0,0 +1,7 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
diff --git a/Exam.cs b/Exam.cs
new file mode 100644
index 0000000..14b28fa
--- /dev/null
+++ b/Exam.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+
+namespace PLG_Exam
+{
+ public class Exam
+ {
+ public string Name { get; set; } = string.Empty;
+ public string Vorname { get; set; } = string.Empty;
+ public DateTime? Datum { get; set; }
+ public List Tabs { get; set; } = new();
+ }
+
+ public class ExamTab
+ {
+ public string Aufgabennummer { get; set; } = string.Empty;
+ public string Überschrift { get; set; } = string.Empty;
+ public string Inhalt { get; set; } = string.Empty;
+ }
+}
diff --git a/MainWindow.axaml b/MainWindow.axaml
index 6b78751..9dfbbb4 100644
--- a/MainWindow.axaml
+++ b/MainWindow.axaml
@@ -4,6 +4,49 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="PLG_Exam.MainWindow"
- Title="PLG_Exam">
- Welcome to Avalonia!
+ Title="PLG Exam">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MainWindow.axaml.cs b/MainWindow.axaml.cs
index 28ba9da..25a44a7 100644
--- a/MainWindow.axaml.cs
+++ b/MainWindow.axaml.cs
@@ -1,11 +1,515 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System.Timers;
+using Avalonia;
using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using Avalonia.Threading;
-namespace PLG_Exam;
-
-public partial class MainWindow : Window
+namespace PLG_Exam
{
- public MainWindow()
+ public partial class MainWindow : Window
{
- InitializeComponent();
+ private int _tabCounter = 0;
+ private bool _isSaved = true;
+ private string? _currentFilePath;
+ public static Exam _currentExam = new();
+
+ public MainWindow()
+ {
+ InitializeComponent();
+ InitializeBackupTimer();
+ AddNewTab();
+
+ this.KeyDown += OnKeyDown;
+ }
+
+ // Event für "Neuen Tab hinzufügen"
+ private void OnAddTabClick(object? sender, RoutedEventArgs e)
+ {
+ AddNewTab();
+ }
+
+ private async void OnKeyDown(object? sender, Avalonia.Input.KeyEventArgs e)
+ {
+ if (e.KeyModifiers.HasFlag(Avalonia.Input.KeyModifiers.Control))
+ {
+ switch (e.Key)
+ {
+ case Avalonia.Input.Key.S:
+ if (e.KeyModifiers.HasFlag(Avalonia.Input.KeyModifiers.Shift))
+ {
+ // Strg + Shift + S: Speichern Unter
+ OnSaveAsClick(sender, e);
+ }
+ else
+ {
+ // Strg + S: Speichern
+ OnSaveClick(sender, e);
+ }
+ break;
+
+ case Avalonia.Input.Key.O:
+ // Strg + O: Öffnen
+ OnOpenClick(sender, e);
+ break;
+
+ case Avalonia.Input.Key.T:
+ // Strg + T: Neuer Tab
+ OnAddTabClick(sender, e);
+ break;
+
+ case Avalonia.Input.Key.N:
+ // Strg + N: Neu
+ OnNewClick(sender, e);
+ break;
+
+ case Avalonia.Input.Key.R:
+ // Strg + R: Abgeben (coming soon)
+ await OnSubmitClick(sender, e);
+ break;
+ }
+ }
+ }
+
+ private async Task OnSubmitClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ await MessageBox.Show(this, "Abgeben-Funktion wird bald verfügbar sein!", "Abgeben", MessageBoxButton.Ok);
+ }
+
+
+ private void AddNewTab()
+ {
+ _tabCounter++;
+
+ var closeButton = new Button
+ {
+ Content = "×",
+ FontSize = 14,
+ HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center,
+ VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center,
+ Width = 20,
+ Height = 20,
+ Background = new SolidColorBrush(Color.FromRgb(123,35,39)),
+ Foreground = new SolidColorBrush(Color.FromRgb(0,0,0)),
+ Padding = new Thickness(0),
+ Margin = new Thickness(5, 0, 0, 0),
+ VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
+ };
+
+ ToolTip.SetTip(closeButton, "Tab schließen");
+
+ var headerStackPanel = new StackPanel
+ {
+ Orientation = Avalonia.Layout.Orientation.Horizontal
+ };
+
+ var tabHeader = new TextBlock
+ {
+ Text = $"{_tabCounter} - Neu",
+ VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
+ };
+
+ headerStackPanel.Children.Add(tabHeader);
+ headerStackPanel.Children.Add(closeButton);
+
+ var tabItem = new TabItem
+ {
+ Header = headerStackPanel,
+ Content = CreateTabContent()
+ };
+
+ // Schließen-Event hinzufügen
+ closeButton.Click += async (sender, e) =>
+ {
+ var result = await MessageBox.Show(this, "Diese Aktion schließt die Aufgabe unwiderruflich. Möchten Sie fortfahren?",
+ "Tab schließen", MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ TabView.Items.Remove(tabItem);
+ _isSaved = false;
+ }
+ };
+
+ TabView.Items.Add(tabItem);
+ TabView.SelectedItem = tabItem;
+ _isSaved = false;
+ }
+
+
+ // Tab entfernen mit Sicherheitsabfrage
+ private async void OnRemoveTabClick(object? sender, RoutedEventArgs e)
+ {
+ if (TabView.SelectedItem is TabItem selectedTab)
+ {
+ var result = await MessageBox.Show(this, "Dieser Tab wird unwiderruflich gelöscht. Möchten Sie fortfahren?",
+ "Tab löschen", MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ TabView.Items.Remove(selectedTab);
+ _isSaved = false;
+ }
+ }
+ }
+
+ // Speichern
+ private async void OnSaveClick(object? sender, RoutedEventArgs e)
+ {
+ if (string.IsNullOrEmpty(_currentFilePath))
+ {
+ await SaveAs();
+ }
+ else
+ {
+ SaveToFile(_currentFilePath);
+ }
+ }
+
+ private async void OnSaveAsClick(object? sender, RoutedEventArgs e)
+ {
+ await SaveAs();
+ }
+
+ private async Task SaveAs()
+ {
+ var saveDialog = new SaveFileDialog
+ {
+ DefaultExtension = "exam",
+ Filters = { new FileDialogFilter { Name = "Exam files", Extensions = { "exam" } } }
+ };
+ var filePath = await saveDialog.ShowAsync(this);
+
+ if (!string.IsNullOrEmpty(filePath))
+ {
+ SaveToFile(filePath);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void SaveToFile(string filePath)
+ {
+ // Update Exam-Daten
+ _currentExam.Name = NameField.Text;
+ _currentExam.Vorname = VornameField.Text;
+ _currentExam.Datum = DatumField.SelectedDate?.UtcDateTime;
+ _currentExam.Tabs = TabView.Items.OfType()
+ .Select(tab =>
+ {
+ var grid = tab.Content as Grid;
+ if (grid == null) return null;
+
+ var aufgabennummer = (grid.Children[0] as Grid)?.Children[0] as TextBox;
+ var ueberschrift = (grid.Children[0] as Grid)?.Children[1] as TextBox;
+ var beschreibung = grid.Children[1] as TextBox;
+
+ return new ExamTab
+ {
+ Aufgabennummer = aufgabennummer?.Text ?? "",
+ Überschrift = ueberschrift?.Text ?? "",
+ Inhalt = beschreibung?.Text ?? ""
+ };
+ })
+ .Where(tab => tab != null)
+ .ToList();
+
+ var json = JsonSerializer.Serialize(_currentExam, new JsonSerializerOptions { WriteIndented = true });
+ File.WriteAllText(filePath, json);
+
+ _currentFilePath = filePath;
+ _isSaved = true;
+ }
+
+ // Öffnen
+ private async void OnOpenClick(object? sender, RoutedEventArgs e)
+ {
+ var openDialog = new OpenFileDialog
+ {
+ AllowMultiple = false,
+ Filters = { new FileDialogFilter { Name = "Exam files", Extensions = { "exam" } } }
+ };
+ var result = await openDialog.ShowAsync(this);
+
+ if (result != null && result.Length > 0)
+ {
+ LoadFromFile(result[0]);
+ }
+ }
+
+ private void LoadFromFile(string filePath)
+ {
+ var fileContent = File.ReadAllText(filePath);
+ var exam = JsonSerializer.Deserialize(fileContent);
+
+ if (exam == null) return;
+
+ // Daten wiederherstellen
+ _currentExam = exam;
+ NameField.Text = exam.Name;
+ VornameField.Text = exam.Vorname;
+ DatumField.SelectedDate = exam.Datum;
+
+ TabView.Items.Clear();
+ _tabCounter = 0;
+
+ foreach (var tab in exam.Tabs)
+ {
+ _tabCounter++;
+ var tabItem = new TabItem
+ {
+ Header = $"{tab.Aufgabennummer} - {tab.Überschrift}",
+ Content = CreateTabContent(tab.Aufgabennummer, tab.Überschrift, tab.Inhalt)
+ };
+ TabView.Items.Add(tabItem);
+ }
+
+ _currentFilePath = filePath;
+ _isSaved = true;
+ }
+
+ // Neu
+ private async void OnNewClick(object? sender, RoutedEventArgs e)
+ {
+ if (!_isSaved)
+ {
+ var result = await MessageBox.Show(this, "Möchten Sie die aktuellen Änderungen speichern?",
+ "Nicht gespeicherte Änderungen", MessageBoxButton.YesNoCancel);
+ if (result == MessageBoxResult.Cancel) return;
+ if (result == MessageBoxResult.Yes) {
+ if((await SaveAs()) != true){
+ return;
+ }
+ }
+ }
+
+ _currentExam = new Exam();
+ NameField.Text = "";
+ VornameField.Text = "";
+ DatumField.SelectedDate = null;
+ TabView.Items.Clear();
+ _tabCounter = 0;
+ _currentFilePath = null;
+ _isSaved = true;
+ }
+
+ private Grid CreateTabContent(string? aufgabennummer = null, string? ueberschrift = null, string? beschreibung = null)
+ {
+ var grid = new Grid();
+ grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(33) });
+ grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
+
+ var headerGrid = new Grid { Margin = new Thickness(0, 0, 0, 10) };
+ headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(250) });
+ headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
+
+ var aufgabenNummerTextBox = new TextBox
+ {
+ Name = "Aufgabennummer",
+ Watermark = "Aufgabennummer",
+ Margin = new Thickness(0,10,0,0),
+ Text = aufgabennummer ?? ""
+ };
+
+ var ueberschriftTextBox = new TextBox
+ {
+ Name = "Überschrift",
+ Watermark = "Überschrift (optional)",
+ Margin = new Thickness(10,10,0,0),
+ Text = ueberschrift ?? ""
+ };
+
+ var beschreibungTextBox = new TextBox
+ {
+ Name = "Beschreibung",
+ AcceptsReturn = true,
+ VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Stretch,
+ Watermark = "Beschreibung",
+ Text = beschreibung ?? "",
+ };
+
+ // Event für dynamische Tab-Umbenennung
+ aufgabenNummerTextBox.KeyUp += (s, e) => UpdateTabHeader(TabView.SelectedItem as TabItem, aufgabenNummerTextBox, ueberschriftTextBox);
+ ueberschriftTextBox.KeyUp += (s, e) => UpdateTabHeader(TabView.SelectedItem as TabItem, aufgabenNummerTextBox, ueberschriftTextBox);
+
+ headerGrid.Children.Add(aufgabenNummerTextBox);
+ headerGrid.Children.Add(ueberschriftTextBox);
+ Grid.SetColumn(aufgabenNummerTextBox, 0);
+ Grid.SetColumn(ueberschriftTextBox, 1);
+
+ grid.Children.Add(headerGrid);
+ grid.Children.Add(beschreibungTextBox);
+ Grid.SetRow(headerGrid, 0);
+ Grid.SetRow(beschreibungTextBox, 1);
+
+ return grid;
+ }
+
+ private void UpdateTabHeader(TabItem? tab, TextBox aufgabennummer, TextBox ueberschrift)
+ {
+ if (tab == null) return;
+
+ var aufgabeText = string.IsNullOrWhiteSpace(aufgabennummer.Text) ? "Neu" : aufgabennummer.Text;
+ var ueberschriftText = string.IsNullOrWhiteSpace(ueberschrift.Text) ? "" : ueberschrift.Text;
+
+ tab.Header = string.IsNullOrWhiteSpace(ueberschriftText)
+ ? aufgabeText
+ : $"{aufgabeText} - {ueberschriftText}";
+ }
+
+
+ //
+ // Backups
+ //
+
+ private const int BackupInterval = 20000; // 20 Sekunden in Millisekunden
+
+
+ private DispatcherTimer _backupTimer;
+
+ private void InitializeBackupTimer()
+ {
+ _backupTimer = new DispatcherTimer
+ {
+ Interval = TimeSpan.FromSeconds(20)
+ };
+ _backupTimer.Tick += (sender, e) => CreateBackup();
+ _backupTimer.Start();
+ }
+
+ private const int MaxBackupFiles = 5; // Maximal 5 Backups
+ private readonly string BackupFolderPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "PLG_Exam_Backup");
+
+
+
+ // Backup erstellen
+ private void CreateBackup()
+ {
+ try
+ {
+ // Ordner erstellen, falls nicht vorhanden
+ if (!Directory.Exists(BackupFolderPath))
+ {
+ Directory.CreateDirectory(BackupFolderPath);
+ }
+
+ // Erstelle Dateinamen mit Zeitstempel
+ var timestamp = DateTime.Now.ToString("yyMMdd_HHmmss");
+ var backupFileName = Path.Combine(BackupFolderPath, $"{timestamp}_backup.exam");
+
+ // Aktuelle Daten in JSON-Format speichern
+ var jsonData = GetCurrentExamDataAsJson();
+ File.WriteAllText(backupFileName, jsonData);
+
+ // Alte Backups löschen, wenn mehr als MaxBackupFiles vorhanden sind
+ CleanupOldBackups();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Backup konnte nicht erstellt werden: {ex.Message}");
+ }
+ }
+
+ // Löscht alte Backups, wenn die maximale Anzahl überschritten wird
+ private void CleanupOldBackups()
+ {
+ var backupFiles = Directory.GetFiles(BackupFolderPath, "*_backup.exam")
+ .OrderBy(File.GetCreationTime)
+ .ToList();
+
+ while (backupFiles.Count > MaxBackupFiles)
+ {
+ var oldestFile = backupFiles.First();
+ File.Delete(oldestFile);
+ backupFiles.RemoveAt(0);
+ }
+ }
+
+ // Löscht alle Backups unwiderruflich (mit Überschreiben)
+ private void DeleteAllBackups()
+ {
+ try
+ {
+ if (Directory.Exists(BackupFolderPath))
+ {
+ foreach (var file in Directory.GetFiles(BackupFolderPath, "*_backup.exam"))
+ {
+ OverwriteAndDeleteFile(file);
+ }
+ Directory.Delete(BackupFolderPath, true);
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Backups konnten nicht gelöscht werden: {ex.Message}");
+ }
+ }
+
+ // Überschreibt eine Datei mit Zufallsdaten und löscht sie anschließend
+ private void OverwriteAndDeleteFile(string filePath)
+ {
+ try
+ {
+ if (!File.Exists(filePath)) return;
+
+ var fileLength = new FileInfo(filePath).Length;
+
+ // Datei mit Zufallsdaten überschreiben
+ using (var stream = new FileStream(filePath, FileMode.Open))
+ {
+ var randomData = new byte[fileLength];
+ new Random().NextBytes(randomData);
+ stream.Write(randomData, 0, randomData.Length);
+ }
+
+ // Datei löschen
+ File.Delete(filePath);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Datei konnte nicht überschrieben und gelöscht werden: {ex.Message}");
+ }
+ }
+
+ // Holt die aktuelle Exam-Daten als JSON
+ private string GetCurrentExamDataAsJson()
+ {
+ _currentExam.Name = NameField.Text;
+ _currentExam.Vorname = VornameField.Text;
+ _currentExam.Datum = DatumField.SelectedDate?.UtcDateTime;
+
+ _currentExam.Tabs = TabView.Items.OfType()
+ .Select(tab =>
+ {
+ var grid = tab.Content as Grid;
+ if (grid == null) return null;
+
+ var aufgabennummer = (grid.Children[0] as Grid)?.Children[0] as TextBox;
+ var ueberschrift = (grid.Children[0] as Grid)?.Children[1] as TextBox;
+ var beschreibung = grid.Children[1] as TextBox;
+
+ return new ExamTab
+ {
+ Aufgabennummer = aufgabennummer?.Text ?? "",
+ Überschrift = ueberschrift?.Text ?? "",
+ Inhalt = beschreibung?.Text ?? ""
+ };
+ })
+ .Where(tab => tab != null)
+ .ToList();
+
+ return System.Text.Json.JsonSerializer.Serialize(_currentExam, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
+ }
+
+
+
+
}
-}
\ No newline at end of file
+}
diff --git a/MessageBox.axaml b/MessageBox.axaml
new file mode 100644
index 0000000..5c12c5c
--- /dev/null
+++ b/MessageBox.axaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MessageBox.axaml.cs b/MessageBox.axaml.cs
new file mode 100644
index 0000000..44f95f3
--- /dev/null
+++ b/MessageBox.axaml.cs
@@ -0,0 +1,76 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using System.Threading.Tasks;
+
+namespace PLG_Exam;
+
+public partial class MessageBox : Window
+{
+ public MessageBox()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public static Task Show(Window? parent, string text, string title, MessageBoxButton buttons = MessageBoxButton.Ok)
+ {
+ var msgbox = new MessageBox()
+ {
+ Title = title
+ };
+ var tb = msgbox.FindControl("Text");
+ if(tb != null) tb.Text = text;
+ var buttonPanel = msgbox.FindControl("Buttons");
+
+ var res = MessageBoxResult.Ok;
+
+ void AddButton(string caption, MessageBoxResult r, bool def = false)
+ {
+ var btn = new Button { Content = caption };
+ btn.Click += (_, __) =>
+ {
+ res = r;
+ msgbox.Close();
+ };
+ if(buttonPanel == null) return;
+ buttonPanel.Children.Add(btn);
+ if (def)
+ res = r;
+ }
+
+ if (buttons == MessageBoxButton.Ok || buttons == MessageBoxButton.OkCancel)
+ AddButton("Ok", MessageBoxResult.Ok, true);
+ if (buttons == MessageBoxButton.YesNo || buttons == MessageBoxButton.YesNoCancel)
+ {
+ AddButton("Yes", MessageBoxResult.Yes);
+ AddButton("No", MessageBoxResult.No, true);
+ }
+
+ if (buttons == MessageBoxButton.OkCancel || buttons == MessageBoxButton.YesNoCancel)
+ AddButton("Cancel", MessageBoxResult.Cancel, true);
+
+
+
+ var tcs = new TaskCompletionSource();
+ msgbox.Closed += delegate { tcs.TrySetResult(res); };
+ if (parent != null)
+ msgbox.ShowDialog(parent);
+ else msgbox.Show();
+ return tcs.Task;
+ }
+}
+public enum MessageBoxButton
+{
+ Ok,
+ OkCancel,
+ YesNo,
+ YesNoCancel
+}
+
+public enum MessageBoxResult
+{
+ Ok,
+ Cancel,
+ Yes,
+ No
+}
\ No newline at end of file
diff --git a/PLG-Exam.sln b/PLG-Exam.sln
new file mode 100644
index 0000000..aafdf64
--- /dev/null
+++ b/PLG-Exam.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PLG-Exam", "PLG-Exam.csproj", "{F120D7C1-1272-40DA-9CBB-0EE2DC779632}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F120D7C1-1272-40DA-9CBB-0EE2DC779632}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F120D7C1-1272-40DA-9CBB-0EE2DC779632}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F120D7C1-1272-40DA-9CBB-0EE2DC779632}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F120D7C1-1272-40DA-9CBB-0EE2DC779632}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {357C5A9A-CDAF-4061-ABC2-8F768DA586D2}
+ EndGlobalSection
+EndGlobal