Files
LehrerApp/LehrerApp.Desktop/Views/Groups/AttendanceHomeworkQuickInputDialog.axaml.cs
T

297 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using LehrerApp.Core.Models;
using LehrerApp.Desktop.ViewModels.Groups;
namespace LehrerApp.Desktop.Views.Groups;
public partial class AttendanceHomeworkQuickInputDialog : Window
{
private bool _leftAltDown;
private bool _rightAltDown;
public AttendanceHomeworkQuickInputDialog()
{
InitializeComponent();
BuildColumns();
// Im Tunnel lauschen, bevor DataGrid/Button einen Modifier-Event konsumieren können.
AddHandler(KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel, handledEventsToo: true);
AddHandler(KeyUpEvent, OnPreviewKeyUp, RoutingStrategies.Tunnel, handledEventsToo: true);
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
StatusGrid.Focus();
if (ViewModel?.SelectedRow is { } row)
StatusGrid.ScrollIntoView(row, null);
}
private void BuildColumns()
{
StatusGrid.Columns.Add(new DataGridTextColumn
{
Header = "Schüler",
Binding = new Binding(nameof(ParticipationStudentRow.Name)),
Width = new DataGridLength(1, DataGridLengthUnitType.Star),
});
StatusGrid.Columns.Add(new DataGridTemplateColumn
{
Header = "Anwesenheit",
Width = new DataGridLength(210, DataGridLengthUnitType.Pixel),
CellTemplate = BuildAttendanceTemplate(),
});
StatusGrid.Columns.Add(new DataGridTemplateColumn
{
Header = "Hausaufgaben",
Width = new DataGridLength(270, DataGridLengthUnitType.Pixel),
CellTemplate = BuildHomeworkTemplate(),
});
}
private IDataTemplate BuildAttendanceTemplate() =>
new FuncDataTemplate<ParticipationStudentRow>((row, _) =>
{
if (row is null) return new TextBlock();
var button = StatusButton(185);
var flyout = new MenuFlyout { Placement = PlacementMode.BottomEdgeAlignedLeft };
AddAttendanceItem(flyout, row, "[1] ✓ Anwesend", AttendanceStatus.Present);
AddAttendanceItem(flyout, row, "[2] ? Entschuldigung offen", AttendanceStatus.ExcusePending);
AddAttendanceItem(flyout, row, "[5] ⊘ Krank, entschuldigt", AttendanceStatus.Excused);
AddAttendanceItem(flyout, row, "[7] ◇ Andere Schulveranstaltung", AttendanceStatus.OtherSchoolEvent);
AddAttendanceItem(flyout, row, "[9] ✕ Geschwänzt", AttendanceStatus.Truant);
AddAttendanceItem(flyout, row, "[0] ! Unentschuldigt", AttendanceStatus.Unexcused);
flyout.Items.Add(new Separator());
AddAttendanceItem(flyout, row, "V Verspätet", AttendanceStatus.Late);
AddAttendanceItem(flyout, row, "V! Erheblich verspätet", AttendanceStatus.SignificantlyLate);
AddAttendanceItem(flyout, row, "A Während des Unterrichts abgängig", AttendanceStatus.LeftDuringClass);
AddAttendanceItem(flyout, row, "L Lerninsel", AttendanceStatus.LearningIsland);
AddAttendanceItem(flyout, row, "S Suspendiert", AttendanceStatus.Suspended);
flyout.Items.Add(new Separator());
AddAttendanceItem(flyout, row, "[X] · Nicht kontrolliert", null);
button.Flyout = flyout;
void Refresh()
{
UpdateButton(button,
string.IsNullOrEmpty(row.AttendanceLabel) ? "·" : row.AttendanceLabel,
row.AttendanceTooltip, AttendanceDisplay.Color(row.Attendance));
}
Refresh();
row.PropertyChanged += (_, args) =>
{
if (args.PropertyName == nameof(ParticipationStudentRow.AttendanceLabel)) Refresh();
};
return Center(button);
});
private IDataTemplate BuildHomeworkTemplate() =>
new FuncDataTemplate<ParticipationStudentRow>((row, _) =>
{
if (row is null) return new TextBlock();
var button = StatusButton(245);
var flyout = new MenuFlyout { Placement = PlacementMode.BottomEdgeAlignedLeft };
AddHomeworkItem(flyout, row, "[⌥1] ✓ Gemacht", HomeworkStatus.Completed);
AddHomeworkItem(flyout, row, "[⌥3] ◐ Teilweise, Rest offen", HomeworkStatus.PartiallyCompleted);
AddHomeworkItem(flyout, row, "[⌥4] ◕ Rest nachgereicht", HomeworkStatus.PartialSubmittedLate);
AddHomeworkItem(flyout, row, "[⌥5] ◒ Rest nicht nachgereicht", HomeworkStatus.PartialMissingOverdue);
AddHomeworkItem(flyout, row, "[⌥7] ! Nicht gemacht, Nachreichen offen", HomeworkStatus.MissingOpen);
AddHomeworkItem(flyout, row, "[⌥8] ↺ Vollständig nachgereicht", HomeworkStatus.SubmittedLate);
AddHomeworkItem(flyout, row, "[⌥0] ✕ Nicht mehr nachgereicht", HomeworkStatus.MissingOverdue);
AddHomeworkItem(flyout, row, "[⌥X] · Keine Hausaufgabe aufgegeben", null);
button.Flyout = flyout;
void Refresh() => UpdateButton(button, row.HomeworkSymbol, row.HomeworkTooltip,
HomeworkDisplay.Color(row.Homework));
Refresh();
row.PropertyChanged += (_, args) =>
{
if (args.PropertyName == nameof(ParticipationStudentRow.HomeworkSymbol)) Refresh();
};
return Center(button);
});
private void AddAttendanceItem(MenuFlyout flyout, ParticipationStudentRow row,
string header, AttendanceStatus? status)
{
var item = new MenuItem { Header = header };
item.Click += (_, _) => ViewModel?.ApplyAttendance(row, status);
flyout.Items.Add(item);
}
private void AddHomeworkItem(MenuFlyout flyout, ParticipationStudentRow row,
string header, HomeworkStatus? status)
{
var item = new MenuItem { Header = header };
item.Click += (_, _) => ViewModel?.ApplyHomework(row, status);
flyout.Items.Add(item);
}
private static Button StatusButton(double width) => new()
{
Width = width,
Height = 29,
Padding = new Thickness(8, 2),
FontSize = 12,
HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center,
};
private static Control Center(Control child) => new Grid
{
Margin = new Thickness(0, 2),
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
Children = { child },
};
private static void UpdateButton(Button button, string symbol, string tooltip, string color)
{
button.Content = $"{symbol} {tooltip}";
ToolTip.SetTip(button, tooltip + " klicken, um Status auszuwählen");
if (Color.TryParse(color, out var parsed))
{
button.Background = new SolidColorBrush(parsed);
button.Foreground = Brushes.White;
button.Opacity = 1;
}
else
{
button.ClearValue(Button.BackgroundProperty);
button.ClearValue(Button.ForegroundProperty);
button.Opacity = 0.48;
}
}
private void OnPreviewKeyDown(object? sender, KeyEventArgs e)
{
if (e.PhysicalKey == PhysicalKey.AltLeft)
{
_leftAltDown = true;
return;
}
if (e.PhysicalKey == PhysicalKey.AltRight)
{
_rightAltDown = true;
return;
}
var vm = ViewModel;
var row = vm?.SelectedRow;
if (vm is null || row is null) return;
var digit = DigitFromEvent(e);
var isX = e.Key == Key.X || e.PhysicalKey == PhysicalKey.X;
var altDown = _leftAltDown || _rightAltDown ||
e.KeyModifiers.HasFlag(KeyModifiers.Alt);
if (altDown)
{
HomeworkStatus? homework = isX ? null : digit switch
{
1 => HomeworkStatus.Completed,
3 => HomeworkStatus.PartiallyCompleted,
4 => HomeworkStatus.PartialSubmittedLate,
5 => HomeworkStatus.PartialMissingOverdue,
7 => HomeworkStatus.MissingOpen,
8 => HomeworkStatus.SubmittedLate,
0 => HomeworkStatus.MissingOverdue,
_ => null,
};
var recognized = isX || digit is 0 or 1 or 3 or 4 or 5 or 7 or 8;
if (recognized)
{
vm.ApplyHomework(row, homework);
BringSelectionIntoView();
e.Handled = true;
return;
}
// Eine mit Alt/Option gedrückte Ziffer darf niemals in den
// Anwesenheitszweig durchfallen, auch wenn sie nicht belegt ist.
if (digit is not null)
{
e.Handled = true;
return;
}
}
AttendanceStatus? attendance = isX ? null : digit switch
{
1 => AttendanceStatus.Present,
2 => AttendanceStatus.ExcusePending,
5 => AttendanceStatus.Excused,
7 => AttendanceStatus.OtherSchoolEvent,
9 => AttendanceStatus.Truant,
0 => AttendanceStatus.Unexcused,
_ => null,
};
var attendanceRecognized = isX || digit is 0 or 1 or 2 or 5 or 7 or 9;
if (attendanceRecognized)
{
vm.ApplyAttendance(row, attendance);
BringSelectionIntoView();
e.Handled = true;
return;
}
switch (e.Key)
{
case Key.Up: vm.MoveSelection(-1); BringSelectionIntoView(); e.Handled = true; break;
case Key.Down or Key.Enter: vm.MoveSelection(1); BringSelectionIntoView(); e.Handled = true; break;
case Key.Escape: Close(); e.Handled = true; break;
default: break;
}
}
private void OnPreviewKeyUp(object? sender, KeyEventArgs e)
{
if (e.PhysicalKey == PhysicalKey.AltLeft) _leftAltDown = false;
if (e.PhysicalKey == PhysicalKey.AltRight) _rightAltDown = false;
}
private AttendanceHomeworkQuickInputViewModel? ViewModel =>
DataContext as AttendanceHomeworkQuickInputViewModel;
private static int? DigitFromEvent(KeyEventArgs e) => e.PhysicalKey switch
{
PhysicalKey.Digit0 => 0,
PhysicalKey.Digit1 => 1,
PhysicalKey.Digit2 => 2,
PhysicalKey.Digit3 => 3,
PhysicalKey.Digit4 => 4,
PhysicalKey.Digit5 => 5,
PhysicalKey.Digit6 => 6,
PhysicalKey.Digit7 => 7,
PhysicalKey.Digit8 => 8,
PhysicalKey.Digit9 => 9,
_ => DigitFromKey(e.Key),
};
private static int? DigitFromKey(Key key) => key switch
{
Key.D0 or Key.NumPad0 => 0,
Key.D1 or Key.NumPad1 => 1,
Key.D2 or Key.NumPad2 => 2,
Key.D3 or Key.NumPad3 => 3,
Key.D4 or Key.NumPad4 => 4,
Key.D5 or Key.NumPad5 => 5,
Key.D6 or Key.NumPad6 => 6,
Key.D7 or Key.NumPad7 => 7,
Key.D8 or Key.NumPad8 => 8,
Key.D9 or Key.NumPad9 => 9,
_ => null,
};
private void BringSelectionIntoView()
{
if (ViewModel?.SelectedRow is { } row)
StatusGrid.ScrollIntoView(row, null);
}
private void OnClose(object? sender, RoutedEventArgs e) => Close();
}