From 7fc6fc6229df724f3696e1a01ff7bd4743605b65 Mon Sep 17 00:00:00 2001 From: Mateusz Skoczek Date: Sun, 7 May 2023 17:39:24 +0200 Subject: [PATCH] update --- TimetableDesigner.Core/BaseGroup.cs | 47 ++ TimetableDesigner.Core/Class.cs | 65 +- TimetableDesigner.Core/Classroom.cs | 53 +- TimetableDesigner.Core/Group.cs | 28 +- .../{IGroup.cs => IUnit.cs} | 2 +- TimetableDesigner.Core/Project.cs | 66 +- TimetableDesigner.Core/Subgroup.cs | 17 +- TimetableDesigner.Core/Teacher.cs | 41 +- TimetableDesigner.Core/TimetableDay.cs | 18 +- .../TimetableDesigner.Core.csproj | 4 + TimetableDesigner.Core/TimetableSpan.cs | 42 +- .../TimetableSpanCollection.cs | 5 +- .../TimetableSpanCollision.cs | 2 +- TimetableDesigner.Core/TimetableTemplate.cs | 18 +- .../JsonSerializableDictionary.cs | 14 + .../TimetableDesigner.Customs.csproj | 4 + TimetableDesigner.Scheduler/Scheduler.cs | 7 + .../TimetableDesigner.Scheduler.csproj | 9 + TimetableDesigner.Tests/TimetableTest.cs | 41 +- TimetableDesigner/App.config | 15 + TimetableDesigner/App.xaml | 2 +- TimetableDesigner/App.xaml.cs | 73 ++- TimetableDesigner/Commands/RelayCommand.cs | 5 +- TimetableDesigner/Controls/ClassControl.xaml | 355 +++++++++++ .../Controls/ClassControl.xaml.cs | 119 ++++ TimetableDesigner/Controls/DynamicGrid.xaml | 10 + .../Controls/DynamicGrid.xaml.cs | 602 ++++++++++++++++++ .../Controls/TimetableEditorControl.xaml | 34 - .../Controls/TimetableEditorControl.xaml.cs | 55 -- ...oleanToGridColumnRowVisibilityConverter.cs | 51 ++ .../BooleanToVisibilityConverter.cs | 49 ++ .../Converters/ByteArrayToColorConverter.cs | 41 ++ ...lorBrightnessIsHigherToBooleanConverter.cs | 39 ++ .../Converters/IUnitVMToUnitNameConverter.cs | 37 ++ .../Converters/IntegerAddConstantConverter.cs | 2 +- .../TimetableDayToColumnNumberConverter.cs | 60 ++ .../TimetableSlotToRowNumberConverter.cs | 59 ++ .../Converters/ViewModelToViewConverter.cs | 15 +- TimetableDesigner/Globals/Path.cs | 14 + .../Properties/Resources.Designer.cs | 381 ++++++++++- TimetableDesigner/Properties/Resources.resx | 134 +++- .../Properties/Settings.Designer.cs | 38 ++ .../Properties/Settings.settings | 9 + TimetableDesigner/Resources/Converters.xaml | 6 + TimetableDesigner/Resources/Images.xaml | 17 +- .../Resources/Images/Autoschedule.png | Bin 0 -> 401 bytes .../Resources/Images/ClassroomBlack.png | Bin 0 -> 616 bytes .../Resources/Images/ClassroomWhite.png | Bin 0 -> 633 bytes .../Resources/Images/CloneBlack.png | Bin 0 -> 436 bytes .../Resources/Images/CloneWhite.png | Bin 0 -> 453 bytes .../Resources/Images/EditBlack.png | Bin 0 -> 486 bytes .../Resources/Images/EditWhite.png | Bin 0 -> 525 bytes TimetableDesigner/Resources/Images/Error.png | Bin 0 -> 1418 bytes .../Resources/Images/GroupBlack.png | Bin 0 -> 910 bytes .../Resources/Images/GroupWhite.png | Bin 0 -> 923 bytes TimetableDesigner/Resources/Images/Info.png | Bin 0 -> 1367 bytes .../Images/{Remove.png => RemoveBlack.png} | Bin .../Resources/Images/RemoveWhite.png | Bin 0 -> 422 bytes .../Resources/Images/StudentsAdd.png | Bin 3999 -> 0 bytes .../Resources/Images/TeacherBlack.png | Bin 0 -> 446 bytes .../Resources/Images/TeacherWhite.png | Bin 0 -> 453 bytes .../Resources/Images/Warning.png | Bin 0 -> 1022 bytes .../Services/FileDialog/FileDialogService.cs | 78 +++ .../Services/FileDialog/IFileDialogService.cs | 17 + .../Services/MessageBox/IMessageBoxService.cs | 6 + .../Services/MessageBox/MessageBoxService.cs | 6 + .../Services/Project/IProjectService.cs | 17 +- .../Services/Project/ProjectError.cs | 92 +++ .../Services/Project/ProjectErrorType.cs | 15 + .../Services/Project/ProjectService.cs | 298 ++++++++- .../Services/Project/RecentProjectEntry.cs | 76 +++ .../Services/Scheduler/ISchedulerService.cs | 19 + .../Services/Scheduler/SchedulerService.cs | 201 ++++++ .../Services/TabNavigation/TabItem.cs | 4 +- .../TabNavigation/TabNavigationService.cs | 4 +- TimetableDesigner/Settings.cs | 28 + TimetableDesigner/TimetableDesigner.csproj | 86 ++- .../{BaseViewViewModel.cs => IModelVM.cs} | 3 +- TimetableDesigner/ViewModels/IRemovableVM.cs | 12 + .../ViewModels/IUnitEditorViewVM.cs | 18 + .../{Models/IGroupViewModel.cs => IUnitVM.cs} | 8 +- .../{BaseModelViewModel.cs => IViewVM.cs} | 4 +- .../ViewModels/Models/BaseGroupVM.cs | 65 ++ .../Models/{ClassViewModel.cs => ClassVM.cs} | 68 +- .../{ClassroomViewModel.cs => ClassroomVM.cs} | 7 +- .../Models/{GroupViewModel.cs => GroupVM.cs} | 42 +- .../ViewModels/Models/ProjectVM.cs | 222 +++++++ .../ViewModels/Models/ProjectViewModel.cs | 188 ------ .../ViewModels/Models/SubgroupVM.cs | 29 + .../ViewModels/Models/SubgroupViewModel.cs | 51 -- .../{TeacherViewModel.cs => TeacherVM.cs} | 21 +- ...ateViewModel.cs => TimetableTemplateVM.cs} | 13 +- ...tViewModel.cs => ClassroomEditorViewVM.cs} | 12 +- ...pEditViewModel.cs => GroupEditorViewVM.cs} | 26 +- .../ViewModels/Views/MainViewModel.cs | 271 -------- .../ViewModels/Views/MainWindowVM.cs | 387 +++++++++++ ...sViewModel.cs => ProjectSettingsViewVM.cs} | 20 +- ...ditViewModel.cs => TeacherEditorViewVM.cs} | 16 +- .../ViewModels/Views/TimetableEditorViewVM.cs | 255 ++++++++ .../{WelcomeViewModel.cs => WelcomeViewVM.cs} | 4 +- ...EditView.xaml => ClassroomEditorView.xaml} | 4 +- ...ew.xaml.cs => ClassroomEditorView.xaml.cs} | 4 +- ...roupEditView.xaml => GroupEditorView.xaml} | 4 +- ...itView.xaml.cs => GroupEditorView.xaml.cs} | 4 +- TimetableDesigner/Views/MainWindow.xaml | 351 ++++++---- .../Views/ProjectSettingsView.xaml | 158 ++--- TimetableDesigner/Views/TeacherEditView.xaml | 121 ---- .../Views/TeacherEditorView.xaml | 127 ++++ ...View.xaml.cs => TeacherEditorView.xaml.cs} | 4 +- .../Views/TimetableEditorView.xaml | 124 ++++ .../Views/TimetableEditorView.xaml.cs | 65 ++ TimetableDesigner/Views/WelcomeView.xaml | 4 +- 112 files changed, 5182 insertions(+), 1182 deletions(-) create mode 100644 TimetableDesigner.Core/BaseGroup.cs rename TimetableDesigner.Core/{IGroup.cs => IUnit.cs} (89%) create mode 100644 TimetableDesigner.Customs/JsonSerializableDictionary.cs create mode 100644 TimetableDesigner.Scheduler/Scheduler.cs create mode 100644 TimetableDesigner.Scheduler/TimetableDesigner.Scheduler.csproj create mode 100644 TimetableDesigner/App.config create mode 100644 TimetableDesigner/Controls/ClassControl.xaml create mode 100644 TimetableDesigner/Controls/ClassControl.xaml.cs create mode 100644 TimetableDesigner/Controls/DynamicGrid.xaml create mode 100644 TimetableDesigner/Controls/DynamicGrid.xaml.cs delete mode 100644 TimetableDesigner/Controls/TimetableEditorControl.xaml delete mode 100644 TimetableDesigner/Controls/TimetableEditorControl.xaml.cs create mode 100644 TimetableDesigner/Converters/BooleanToGridColumnRowVisibilityConverter.cs create mode 100644 TimetableDesigner/Converters/BooleanToVisibilityConverter.cs create mode 100644 TimetableDesigner/Converters/ByteArrayToColorConverter.cs create mode 100644 TimetableDesigner/Converters/ColorBrightnessIsHigherToBooleanConverter.cs create mode 100644 TimetableDesigner/Converters/IUnitVMToUnitNameConverter.cs create mode 100644 TimetableDesigner/Converters/TimetableDayToColumnNumberConverter.cs create mode 100644 TimetableDesigner/Converters/TimetableSlotToRowNumberConverter.cs create mode 100644 TimetableDesigner/Globals/Path.cs create mode 100644 TimetableDesigner/Properties/Settings.Designer.cs create mode 100644 TimetableDesigner/Properties/Settings.settings create mode 100644 TimetableDesigner/Resources/Images/Autoschedule.png create mode 100644 TimetableDesigner/Resources/Images/ClassroomBlack.png create mode 100644 TimetableDesigner/Resources/Images/ClassroomWhite.png create mode 100644 TimetableDesigner/Resources/Images/CloneBlack.png create mode 100644 TimetableDesigner/Resources/Images/CloneWhite.png create mode 100644 TimetableDesigner/Resources/Images/EditBlack.png create mode 100644 TimetableDesigner/Resources/Images/EditWhite.png create mode 100644 TimetableDesigner/Resources/Images/Error.png create mode 100644 TimetableDesigner/Resources/Images/GroupBlack.png create mode 100644 TimetableDesigner/Resources/Images/GroupWhite.png create mode 100644 TimetableDesigner/Resources/Images/Info.png rename TimetableDesigner/Resources/Images/{Remove.png => RemoveBlack.png} (100%) create mode 100644 TimetableDesigner/Resources/Images/RemoveWhite.png delete mode 100644 TimetableDesigner/Resources/Images/StudentsAdd.png create mode 100644 TimetableDesigner/Resources/Images/TeacherBlack.png create mode 100644 TimetableDesigner/Resources/Images/TeacherWhite.png create mode 100644 TimetableDesigner/Resources/Images/Warning.png create mode 100644 TimetableDesigner/Services/FileDialog/FileDialogService.cs create mode 100644 TimetableDesigner/Services/FileDialog/IFileDialogService.cs create mode 100644 TimetableDesigner/Services/Project/ProjectError.cs create mode 100644 TimetableDesigner/Services/Project/ProjectErrorType.cs create mode 100644 TimetableDesigner/Services/Project/RecentProjectEntry.cs create mode 100644 TimetableDesigner/Services/Scheduler/ISchedulerService.cs create mode 100644 TimetableDesigner/Services/Scheduler/SchedulerService.cs create mode 100644 TimetableDesigner/Settings.cs rename TimetableDesigner/ViewModels/{BaseViewViewModel.cs => IModelVM.cs} (64%) create mode 100644 TimetableDesigner/ViewModels/IRemovableVM.cs create mode 100644 TimetableDesigner/ViewModels/IUnitEditorViewVM.cs rename TimetableDesigner/ViewModels/{Models/IGroupViewModel.cs => IUnitVM.cs} (65%) rename TimetableDesigner/ViewModels/{BaseModelViewModel.cs => IViewVM.cs} (58%) create mode 100644 TimetableDesigner/ViewModels/Models/BaseGroupVM.cs rename TimetableDesigner/ViewModels/Models/{ClassViewModel.cs => ClassVM.cs} (59%) rename TimetableDesigner/ViewModels/Models/{ClassroomViewModel.cs => ClassroomVM.cs} (88%) rename TimetableDesigner/ViewModels/Models/{GroupViewModel.cs => GroupVM.cs} (53%) create mode 100644 TimetableDesigner/ViewModels/Models/ProjectVM.cs delete mode 100644 TimetableDesigner/ViewModels/Models/ProjectViewModel.cs create mode 100644 TimetableDesigner/ViewModels/Models/SubgroupVM.cs delete mode 100644 TimetableDesigner/ViewModels/Models/SubgroupViewModel.cs rename TimetableDesigner/ViewModels/Models/{TeacherViewModel.cs => TeacherVM.cs} (78%) rename TimetableDesigner/ViewModels/Models/{TimetableTemplateViewModel.cs => TimetableTemplateVM.cs} (79%) rename TimetableDesigner/ViewModels/Views/{ClassroomEditViewModel.cs => ClassroomEditorViewVM.cs} (79%) rename TimetableDesigner/ViewModels/Views/{GroupEditViewModel.cs => GroupEditorViewVM.cs} (80%) delete mode 100644 TimetableDesigner/ViewModels/Views/MainViewModel.cs create mode 100644 TimetableDesigner/ViewModels/Views/MainWindowVM.cs rename TimetableDesigner/ViewModels/Views/{ProjectSettingsViewModel.cs => ProjectSettingsViewVM.cs} (87%) rename TimetableDesigner/ViewModels/Views/{TeacherEditViewModel.cs => TeacherEditorViewVM.cs} (90%) create mode 100644 TimetableDesigner/ViewModels/Views/TimetableEditorViewVM.cs rename TimetableDesigner/ViewModels/Views/{WelcomeViewModel.cs => WelcomeViewVM.cs} (75%) rename TimetableDesigner/Views/{ClassroomEditView.xaml => ClassroomEditorView.xaml} (96%) rename TimetableDesigner/Views/{ClassroomEditView.xaml.cs => ClassroomEditorView.xaml.cs} (83%) rename TimetableDesigner/Views/{GroupEditView.xaml => GroupEditorView.xaml} (97%) rename TimetableDesigner/Views/{GroupEditView.xaml.cs => GroupEditorView.xaml.cs} (84%) delete mode 100644 TimetableDesigner/Views/TeacherEditView.xaml create mode 100644 TimetableDesigner/Views/TeacherEditorView.xaml rename TimetableDesigner/Views/{TeacherEditView.xaml.cs => TeacherEditorView.xaml.cs} (84%) create mode 100644 TimetableDesigner/Views/TimetableEditorView.xaml create mode 100644 TimetableDesigner/Views/TimetableEditorView.xaml.cs diff --git a/TimetableDesigner.Core/BaseGroup.cs b/TimetableDesigner.Core/BaseGroup.cs new file mode 100644 index 0000000..7771057 --- /dev/null +++ b/TimetableDesigner.Core/BaseGroup.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TimetableDesigner.Core +{ + public abstract class BaseGroup : IUnit + { + #region FIELDS + + private string _name; + private string _shortName; + + #endregion + + + + #region PROPERTIES + + public string Name + { + get => _name; + set => _name = value; + } + public string ShortName + { + get => _shortName; + set => _shortName = value; + } + + #endregion + + + + #region CONSTRUCTORS + + public BaseGroup() + { + _name = string.Empty; + _shortName = string.Empty; + } + + #endregion + } +} diff --git a/TimetableDesigner.Core/Class.cs b/TimetableDesigner.Core/Class.cs index c401feb..44b325e 100644 --- a/TimetableDesigner.Core/Class.cs +++ b/TimetableDesigner.Core/Class.cs @@ -6,14 +6,60 @@ using System.Threading.Tasks; namespace TimetableDesigner.Core { + [Serializable] public class Class { + #region FIELDS + + private string _name; + private Teacher? _teacher; + private BaseGroup? _group; + private Classroom? _classroom; + private TimetableDay? _day; + private TimetableSpan? _slot; + private byte[] _color; + + #endregion + + + #region PROPERTIES - public string Name { get; set; } - public Teacher? Teacher { get; set; } - public IGroup? Group { get; set; } - public Classroom? Classroom { get; set; } + public string Name + { + get => _name; + set => _name = value; + } + public Teacher? Teacher + { + get => _teacher; + set => _teacher = value; + } + public BaseGroup? Group + { + get => _group; + set => _group = value; + } + public Classroom? Classroom + { + get => _classroom; + set => _classroom = value; + } + public TimetableDay? Day + { + get => _day; + set => _day = value; + } + public TimetableSpan? Slot + { + get => _slot; + set => _slot = value; + } + public byte[] Color + { + get => _color; + set => _color = value; + } #endregion @@ -23,10 +69,13 @@ namespace TimetableDesigner.Core public Class() { - Name = string.Empty; - Teacher = null; - Group = null; - Classroom = null; + _name = string.Empty; + _teacher = null; + _group = null; + _classroom = null; + _day = null; + _slot = null; + _color = new byte[3] { 0xFA, 0x5A, 0x5A }; } #endregion diff --git a/TimetableDesigner.Core/Classroom.cs b/TimetableDesigner.Core/Classroom.cs index a3d7573..d3950b2 100644 --- a/TimetableDesigner.Core/Classroom.cs +++ b/TimetableDesigner.Core/Classroom.cs @@ -7,14 +7,48 @@ using System.Threading.Tasks; namespace TimetableDesigner.Core { - public class Classroom + [Serializable] + public class Classroom : IUnit { + #region FIELDS + + private string _name; + private string _shortName; + private string _description; + private bool _isCapacityLimited; + private uint _capacity; + + #endregion + + + #region PROPERTIES - public string Name { get; set; } - public string Description { get; set; } - public bool IsCapacityLimited { get; set; } - public uint Capacity { get; set; } + public string Name + { + get => _name; + set => _name = value; + } + public string ShortName + { + get => _shortName; + set => _shortName = value; + } + public string Description + { + get => _description; + set => _description = value; + } + public bool IsCapacityLimited + { + get => _isCapacityLimited; + set => _isCapacityLimited = value; + } + public uint Capacity + { + get => _capacity; + set => _capacity = value; + } #endregion @@ -24,10 +58,11 @@ namespace TimetableDesigner.Core public Classroom() { - Name = string.Empty; - Description = string.Empty; - IsCapacityLimited = false; - Capacity = 1; + _name = string.Empty; + _shortName = string.Empty; + _description = string.Empty; + _isCapacityLimited = false; + _capacity = 1; } #endregion diff --git a/TimetableDesigner.Core/Group.cs b/TimetableDesigner.Core/Group.cs index e4c9e55..8a1d4bd 100644 --- a/TimetableDesigner.Core/Group.cs +++ b/TimetableDesigner.Core/Group.cs @@ -6,13 +6,26 @@ using System.Threading.Tasks; namespace TimetableDesigner.Core { - public class Group : IGroup + [Serializable] + public class Group : BaseGroup { + #region FIELDS + + private string _description; + private HashSet _assignedSubgroups; + + #endregion + + + #region PROPERTIES - public string Name { get; set; } - public string Description { get; set; } - public ICollection AssignedSubgroups { get; set; } + public string Description + { + get => _description; + set => _description = value; + } + public ICollection AssignedSubgroups => _assignedSubgroups; #endregion @@ -20,11 +33,10 @@ namespace TimetableDesigner.Core #region CONSTRUCTORS - public Group() + public Group() : base() { - Name = string.Empty; - Description = string.Empty; - AssignedSubgroups = new HashSet(); + _description = string.Empty; + _assignedSubgroups = new HashSet(); } #endregion diff --git a/TimetableDesigner.Core/IGroup.cs b/TimetableDesigner.Core/IUnit.cs similarity index 89% rename from TimetableDesigner.Core/IGroup.cs rename to TimetableDesigner.Core/IUnit.cs index d4d6b83..fa02155 100644 --- a/TimetableDesigner.Core/IGroup.cs +++ b/TimetableDesigner.Core/IUnit.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace TimetableDesigner.Core { - public interface IGroup + public interface IUnit { #region PROPERTIES diff --git a/TimetableDesigner.Core/Project.cs b/TimetableDesigner.Core/Project.cs index 85d9cb2..1419e0f 100644 --- a/TimetableDesigner.Core/Project.cs +++ b/TimetableDesigner.Core/Project.cs @@ -9,17 +9,47 @@ namespace TimetableDesigner.Core [Serializable] public class Project { + #region FIELDS + + private Guid _guid; + private string _name; + private string _author; + private string _description; + private TimetableTemplate _timetableTemplate; + private HashSet _classrooms; + private HashSet _teachers; + private HashSet _groups; + private HashSet _subgroups; + private HashSet _classes; + + #endregion + + + #region PROPERTIES - public string Name { get; set; } - public string Author { get; set; } - public string Description { get; set; } - public TimetableTemplate TimetableTemplate { get; set; } - public ICollection Classrooms { get; set; } - public ICollection Teachers { get; set; } - public ICollection Groups { get; set; } - public ICollection Subgroups { get; set; } - public ICollection Classes { get; set; } + public Guid Guid => _guid; + public string Name + { + get => _name; + set => _name = value; + } + public string Author + { + get => _author; + set => _author = value; + } + public string Description + { + get => _description; + set => _description = value; + } + public TimetableTemplate TimetableTemplate => _timetableTemplate; + public ICollection Classrooms => _classrooms; + public ICollection Teachers => _teachers; + public ICollection Groups => _groups; + public ICollection Subgroups => _subgroups; + public ICollection Classes => _classes; #endregion @@ -29,14 +59,16 @@ namespace TimetableDesigner.Core public Project() { - Name = string.Empty; - Author = string.Empty; - Description = string.Empty; - TimetableTemplate = new TimetableTemplate(); - Classrooms = new HashSet(); - Teachers = new HashSet(); - Groups = new HashSet(); - Subgroups = new HashSet(); + _guid = Guid.NewGuid(); + _name = string.Empty; + _author = string.Empty; + _description = string.Empty; + _timetableTemplate = new TimetableTemplate(); + _classrooms = new HashSet(); + _teachers = new HashSet(); + _groups = new HashSet(); + _subgroups = new HashSet(); + _classes = new HashSet(); } #endregion diff --git a/TimetableDesigner.Core/Subgroup.cs b/TimetableDesigner.Core/Subgroup.cs index 2079fed..22c0c40 100644 --- a/TimetableDesigner.Core/Subgroup.cs +++ b/TimetableDesigner.Core/Subgroup.cs @@ -6,22 +6,13 @@ using System.Threading.Tasks; namespace TimetableDesigner.Core { - public class Subgroup : IGroup + [Serializable] + public class Subgroup : BaseGroup { - #region PROPERTIES - - public string Name { get; set; } - - #endregion - - - #region CONSTRUCTORS - public Subgroup() - { - Name = string.Empty; - } + public Subgroup() : base() + { } #endregion } diff --git a/TimetableDesigner.Core/Teacher.cs b/TimetableDesigner.Core/Teacher.cs index 479f677..a201c6b 100644 --- a/TimetableDesigner.Core/Teacher.cs +++ b/TimetableDesigner.Core/Teacher.cs @@ -3,16 +3,42 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using TimetableDesigner.Customs; namespace TimetableDesigner.Core { - public class Teacher + [Serializable] + public class Teacher : IUnit { + #region FIELDS + + private string _name; + private string _shortName; + private string _description; + private JsonSerializableDictionary _availabilityHours; + + #endregion + + + #region PROPERTIES - public string Name { get; set; } - public string Description { get; set; } - public IDictionary AvailabilityHours { get; set; } + public string Name + { + get => _name; + set => _name = value; + } + public string ShortName + { + get => _shortName; + set => _shortName = value; + } + public string Description + { + get => _description; + set => _description = value; + } + public IDictionary AvailabilityHours => _availabilityHours; #endregion @@ -22,9 +48,10 @@ namespace TimetableDesigner.Core public Teacher() { - Name = string.Empty; - Description = string.Empty; - AvailabilityHours = new Dictionary(); + _name = string.Empty; + _shortName = string.Empty; + _description = string.Empty; + _availabilityHours = new JsonSerializableDictionary(); } #endregion diff --git a/TimetableDesigner.Core/TimetableDay.cs b/TimetableDesigner.Core/TimetableDay.cs index ff7bfb8..bfc4aaa 100644 --- a/TimetableDesigner.Core/TimetableDay.cs +++ b/TimetableDesigner.Core/TimetableDay.cs @@ -9,9 +9,21 @@ namespace TimetableDesigner.Core [Serializable] public class TimetableDay { + #region FIELDS + + private string _name; + + #endregion + + + #region PROPERTIES - public string Name { get; set; } + public string Name + { + get => _name; + set => _name = value; + } #endregion @@ -20,8 +32,8 @@ namespace TimetableDesigner.Core #region CONSTRUCTORS public TimetableDay(string name) - { - Name = name; + { + _name = name; } #endregion diff --git a/TimetableDesigner.Core/TimetableDesigner.Core.csproj b/TimetableDesigner.Core/TimetableDesigner.Core.csproj index cb6a55a..5417903 100644 --- a/TimetableDesigner.Core/TimetableDesigner.Core.csproj +++ b/TimetableDesigner.Core/TimetableDesigner.Core.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/TimetableDesigner.Core/TimetableSpan.cs b/TimetableDesigner.Core/TimetableSpan.cs index 273c930..41d6ec1 100644 --- a/TimetableDesigner.Core/TimetableSpan.cs +++ b/TimetableDesigner.Core/TimetableSpan.cs @@ -1,14 +1,24 @@ using System; +using System.Diagnostics; namespace TimetableDesigner.Core { [Serializable] public class TimetableSpan { + #region FIELDS + + private TimeOnly _from; + private TimeOnly _to; + + #endregion + + + #region PROPERTIES - public TimeOnly From { get; private set; } - public TimeOnly To { get; private set; } + public TimeOnly From => _from; + public TimeOnly To => _to; #endregion @@ -20,11 +30,11 @@ namespace TimetableDesigner.Core { if (to <= from) { - throw new ArgumentException("\"to\" cannot be less or equal to \"from\""); + throw new ArgumentException("Ending value (\"to\") of TimetableSpan have to be greater than starting value (\"from\")"); } - From = from; - To = to; + _from = from; + _to = to; } #endregion @@ -33,27 +43,33 @@ namespace TimetableDesigner.Core #region PUBLIC METHODS - internal TimetableSpanCollision CheckCollision(TimetableSpan slot) + public override bool Equals(object? obj) => obj is TimetableSpan slot && From == slot.From && To == slot.To; + + public override int GetHashCode() => HashCode.Combine(From, To); + + public override string? ToString() => $"{From} - {To}"; + + public TimetableSpanCollision CheckCollision(TimetableSpan slot) { if (slot.To <= this.From) { return TimetableSpanCollision.CheckedSlotBefore; } - else if (this.To <= slot.From) + else if (this.To <= slot.From) { return TimetableSpanCollision.CheckedSlotAfter; } else { - if (this.From < slot.From && slot.To < this.To) + if (this.From <= slot.From && slot.To <= this.To) { return TimetableSpanCollision.CheckedSlotIn; } - else if (this.From < slot.From && slot.From < this.To && this.To < slot.To) + else if (this.From < slot.From && slot.From < this.To && this.To <= slot.To) { return TimetableSpanCollision.CheckedSlotFromIn; } - else if (slot.From < this.From && this.From < slot.To && slot.To < this.To) + else if (slot.From < this.From && this.From < slot.To && slot.To <= this.To) { return TimetableSpanCollision.CheckedSlotToIn; } @@ -64,12 +80,6 @@ namespace TimetableDesigner.Core } } - public override bool Equals(object? obj) => obj is TimetableSpan slot && From == slot.From && To == slot.To; - - public override int GetHashCode() => HashCode.Combine(From, To); - - public override string? ToString() => $"{From}-{To}"; - #endregion } } diff --git a/TimetableDesigner.Core/TimetableSpanCollection.cs b/TimetableDesigner.Core/TimetableSpanCollection.cs index c1df85e..21de77f 100644 --- a/TimetableDesigner.Core/TimetableSpanCollection.cs +++ b/TimetableDesigner.Core/TimetableSpanCollection.cs @@ -7,11 +7,12 @@ using System.Threading.Tasks; namespace TimetableDesigner.Core { + [Serializable] public class TimetableSpanCollection : ICollection { #region FIELDS - private IList _list; + private List _list; #endregion @@ -20,7 +21,7 @@ namespace TimetableDesigner.Core #region PROPERTIES public int Count => _list.Count; - public bool IsReadOnly => _list.IsReadOnly; + public bool IsReadOnly => ((ICollection)_list).IsReadOnly; #endregion diff --git a/TimetableDesigner.Core/TimetableSpanCollision.cs b/TimetableDesigner.Core/TimetableSpanCollision.cs index 6b5229f..49689dc 100644 --- a/TimetableDesigner.Core/TimetableSpanCollision.cs +++ b/TimetableDesigner.Core/TimetableSpanCollision.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace TimetableDesigner.Core { - internal enum TimetableSpanCollision + public enum TimetableSpanCollision { CheckedSlotBefore, CheckedSlotAfter, diff --git a/TimetableDesigner.Core/TimetableTemplate.cs b/TimetableDesigner.Core/TimetableTemplate.cs index 98c2626..6fb5fde 100644 --- a/TimetableDesigner.Core/TimetableTemplate.cs +++ b/TimetableDesigner.Core/TimetableTemplate.cs @@ -20,8 +20,8 @@ namespace TimetableDesigner.Core #region PROPERTIES - public IEnumerable Days => _days; - public IEnumerable Slots => _slots; + public ICollection Days => _days; + public ICollection Slots => _slots; #endregion @@ -36,19 +36,5 @@ namespace TimetableDesigner.Core } #endregion - - - - #region PUBLIC METHODS - - public void AddDay(TimetableDay name) => _days.Add(name); - - public bool RemoveDay(TimetableDay day) => _days.Remove(day); - - public void AddSlot(TimetableSpan slot) => _slots.Add(slot); - - public bool RemoveSlot(TimetableSpan slot) => _slots.Remove(slot); - - #endregion } } diff --git a/TimetableDesigner.Customs/JsonSerializableDictionary.cs b/TimetableDesigner.Customs/JsonSerializableDictionary.cs new file mode 100644 index 0000000..d6530d6 --- /dev/null +++ b/TimetableDesigner.Customs/JsonSerializableDictionary.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TimetableDesigner.Customs +{ + [JsonArray] + public class JsonSerializableDictionary : Dictionary where TKey : notnull + { + } +} diff --git a/TimetableDesigner.Customs/TimetableDesigner.Customs.csproj b/TimetableDesigner.Customs/TimetableDesigner.Customs.csproj index cfadb03..51eb283 100644 --- a/TimetableDesigner.Customs/TimetableDesigner.Customs.csproj +++ b/TimetableDesigner.Customs/TimetableDesigner.Customs.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/TimetableDesigner.Scheduler/Scheduler.cs b/TimetableDesigner.Scheduler/Scheduler.cs new file mode 100644 index 0000000..b632a26 --- /dev/null +++ b/TimetableDesigner.Scheduler/Scheduler.cs @@ -0,0 +1,7 @@ +namespace TimetableDesigner.Scheduler +{ + public class Class1 + { + + } +} \ No newline at end of file diff --git a/TimetableDesigner.Scheduler/TimetableDesigner.Scheduler.csproj b/TimetableDesigner.Scheduler/TimetableDesigner.Scheduler.csproj new file mode 100644 index 0000000..cfadb03 --- /dev/null +++ b/TimetableDesigner.Scheduler/TimetableDesigner.Scheduler.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/TimetableDesigner.Tests/TimetableTest.cs b/TimetableDesigner.Tests/TimetableTest.cs index b691ee1..2d0c8c2 100644 --- a/TimetableDesigner.Tests/TimetableTest.cs +++ b/TimetableDesigner.Tests/TimetableTest.cs @@ -42,12 +42,9 @@ namespace TimetableDesigner.Tests { TimetableTemplate model = new TimetableTemplate(); - TimetableDay day = new TimetableDay - { - Name = "Monday" - }; + TimetableDay day = new TimetableDay("Monday"); - model.AddDay(day); + model.Days.Add(day); Assert.AreEqual(1, model.Days.Count()); Assert.AreEqual(day, model.Days.ToList()[0]); @@ -60,7 +57,7 @@ namespace TimetableDesigner.Tests TimetableSpan slot = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)); - model.AddSlot(slot); + model.Slots.Add(slot); Assert.AreEqual(1, model.Slots.Count()); Assert.AreEqual(new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)), model.Slots.ToList()[0]); @@ -74,8 +71,8 @@ namespace TimetableDesigner.Tests TimetableSpan slot1 = new TimetableSpan(new TimeOnly(8, 15), new TimeOnly(9, 0)); TimetableSpan slot2 = new TimetableSpan(new TimeOnly(9, 15), new TimeOnly(10, 0)); - model.AddSlot(slot1); - model.AddSlot(slot2); + model.Slots.Add(slot1); + model.Slots.Add(slot2); Assert.AreEqual(2, model.Slots.Count()); Assert.AreEqual(new TimetableSpan(new TimeOnly(8, 15), new TimeOnly(9, 0)), model.Slots.ToList()[0]); @@ -90,8 +87,8 @@ namespace TimetableDesigner.Tests TimetableSpan slot1 = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)); TimetableSpan slot2 = new TimetableSpan(new TimeOnly(9, 0), new TimeOnly(10, 0)); - model.AddSlot(slot1); - model.AddSlot(slot2); + model.Slots.Add(slot1); + model.Slots.Add(slot2); Assert.AreEqual(2, model.Slots.Count()); Assert.AreEqual(new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)), model.Slots.ToList()[0]); @@ -107,8 +104,8 @@ namespace TimetableDesigner.Tests TimetableSpan slot1 = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 30)); TimetableSpan slot2 = new TimetableSpan(new TimeOnly(8, 30), new TimeOnly(10, 0)); - model.AddSlot(slot1); - model.AddSlot(slot2); + model.Slots.Add(slot1); + model.Slots.Add(slot2); Assert.Fail(); } @@ -123,9 +120,9 @@ namespace TimetableDesigner.Tests TimetableSpan slot2 = new TimetableSpan(new TimeOnly(10, 0), new TimeOnly(11, 0)); TimetableSpan slot3 = new TimetableSpan(new TimeOnly(8, 59), new TimeOnly(10, 1)); - model.AddSlot(slot1); - model.AddSlot(slot2); - model.AddSlot(slot3); + model.Slots.Add(slot1); + model.Slots.Add(slot2); + model.Slots.Add(slot3); Assert.Fail(); } @@ -143,13 +140,13 @@ namespace TimetableDesigner.Tests TimetableSpan slot6 = new TimetableSpan(new TimeOnly(9, 0), new TimeOnly(10, 0)); TimetableSpan slot7 = new TimetableSpan(new TimeOnly(11, 0), new TimeOnly(12, 0)); - model.AddSlot(slot1); - model.AddSlot(slot2); - model.AddSlot(slot3); - model.AddSlot(slot4); - model.AddSlot(slot5); - model.AddSlot(slot6); - model.AddSlot(slot7); + model.Slots.Add(slot1); + model.Slots.Add(slot2); + model.Slots.Add(slot3); + model.Slots.Add(slot4); + model.Slots.Add(slot5); + model.Slots.Add(slot6); + model.Slots.Add(slot7); List slots = model.Slots.ToList(); diff --git a/TimetableDesigner/App.config b/TimetableDesigner/App.config new file mode 100644 index 0000000..59a896c --- /dev/null +++ b/TimetableDesigner/App.config @@ -0,0 +1,15 @@ + + + + +
+ + + + + + 10 + + + + \ No newline at end of file diff --git a/TimetableDesigner/App.xaml b/TimetableDesigner/App.xaml index 15164af..f60bc5c 100644 --- a/TimetableDesigner/App.xaml +++ b/TimetableDesigner/App.xaml @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" ShutdownMode="OnMainWindowClose" - StartupUri="Views/MainWindow.xaml"> + Startup="OnStartup"> diff --git a/TimetableDesigner/App.xaml.cs b/TimetableDesigner/App.xaml.cs index 3fb8fd4..391ccd0 100644 --- a/TimetableDesigner/App.xaml.cs +++ b/TimetableDesigner/App.xaml.cs @@ -1,25 +1,88 @@ -using System; +global using TimetableDesigner.Services; +using Microsoft.ML.Data; +using System; using System.Collections.Generic; using System.ComponentModel.Design; using System.Configuration; using System.Data; +using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; -using TimetableDesigner.Services; +using TimetableDesigner.Services.FileDialog; using TimetableDesigner.Services.MessageBox; using TimetableDesigner.Services.Project; +using TimetableDesigner.Services.Scheduler; using TimetableDesigner.Services.TabNavigation; +using TimetableDesigner.ViewModels.Views; +using TimetableDesigner.Views; namespace TimetableDesigner { public partial class App : Application { + #region FIELDS + + private IMessageBoxService _messageBoxService; + private IFileDialogService _fileDialogService; + private ITabNavigationService _tabNavigationService; + private IProjectService _projectService; + private ISchedulerService _schedulerService; + + #endregion + + + + #region CONSTRUCTORS + public App() { - ServiceProvider.Instance.AddService(new MessageBoxService()); - ServiceProvider.Instance.AddService(new TabNavigationService()); - ServiceProvider.Instance.AddService(new ProjectService()); + _messageBoxService = new MessageBoxService(); + _fileDialogService = new FileDialogService(); + _tabNavigationService = new TabNavigationService(); + _projectService = new ProjectService(); + _schedulerService = new SchedulerService(_projectService); + + ServiceProvider.Instance.AddService(_messageBoxService); + ServiceProvider.Instance.AddService(_fileDialogService); + ServiceProvider.Instance.AddService(_tabNavigationService); + ServiceProvider.Instance.AddService(_projectService); + ServiceProvider.Instance.AddService(_schedulerService); } + + #endregion + + + + #region PRIVATE METHODS + + private void OnStartup(object sender, StartupEventArgs e) + { + if (!Directory.Exists(Globals.Path.ApplicationData)) + { + Directory.CreateDirectory(Globals.Path.ApplicationData); + } + + _projectService.LoadRecentProjectsList(); + + MainWindow mainWindow = new MainWindow(); + mainWindow.Show(); + + if (e.Args.Length > 0 && File.Exists(e.Args[0])) + { + _projectService.Load(e.Args[0]); + } + else + { + TabItem welcomeTab = new TabItem() + { + Title = TimetableDesigner.Properties.Resources.Tabs_Welcome, + ViewModel = new WelcomeViewVM() + }; + _tabNavigationService.AddAndActivate(welcomeTab); + } + } + + #endregion } } diff --git a/TimetableDesigner/Commands/RelayCommand.cs b/TimetableDesigner/Commands/RelayCommand.cs index 4873f1b..050b2b7 100644 --- a/TimetableDesigner/Commands/RelayCommand.cs +++ b/TimetableDesigner/Commands/RelayCommand.cs @@ -39,10 +39,7 @@ namespace TimetableDesigner.Commands public bool CanExecute(object parameter) => _canExecute == null || _canExecute((T)parameter); - public void Execute(object parameter) - { - _execute((T)parameter); - } + public void Execute(object parameter) => _execute((T)parameter); #endregion diff --git a/TimetableDesigner/Controls/ClassControl.xaml b/TimetableDesigner/Controls/ClassControl.xaml new file mode 100644 index 0000000..131fd12 --- /dev/null +++ b/TimetableDesigner/Controls/ClassControl.xaml @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TimetableDesigner/Controls/ClassControl.xaml.cs b/TimetableDesigner/Controls/ClassControl.xaml.cs new file mode 100644 index 0000000..e87bc3e --- /dev/null +++ b/TimetableDesigner/Controls/ClassControl.xaml.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using TimetableDesigner.Core; +using TimetableDesigner.Services; +using TimetableDesigner.Services.Project; +using TimetableDesigner.Services.TabNavigation; +using TimetableDesigner.ViewModels.Models; +using TimetableDesigner.ViewModels.Models.Base; +using TimetableDesigner.ViewModels.Views; + +namespace TimetableDesigner.Controls +{ + public partial class ClassControl : UserControl + { + #region FIELDS + + private IProjectService _projectService; + private ITabNavigationService _tabNavigationService; + + #endregion + + + + #region PROPERTIES + + private static DependencyProperty RemoveButtonCommandProperty = DependencyProperty.Register( + "RemoveButtonCommand", + typeof(ICommand), + typeof(ClassControl), + new PropertyMetadata(null) + ); + public ICommand RemoveButtonCommand + { + get => (ICommand)GetValue(RemoveButtonCommandProperty); + set => SetValue(RemoveButtonCommandProperty, value); + } + + private static DependencyProperty RemoveButtonCommandParameterProperty = DependencyProperty.Register( + "RemoveButtonCommandParameter", + typeof(object), + typeof(ClassControl), + new PropertyMetadata(null) + ); + public object RemoveButtonCommandParameter + { + get => GetValue(RemoveButtonCommandParameterProperty); + set => SetValue(RemoveButtonCommandParameterProperty, value); + } + + private static DependencyProperty CloneButtonCommandProperty = DependencyProperty.Register( + "CloneButtonCommand", + typeof(ICommand), + typeof(ClassControl), + new PropertyMetadata(null) + ); + public ICommand CloneButtonCommand + { + get => (ICommand)GetValue(CloneButtonCommandProperty); + set => SetValue(CloneButtonCommandProperty, value); + } + + private static DependencyProperty CloneButtonCommandParameterProperty = DependencyProperty.Register( + "CloneButtonCommandParameter", + typeof(object), + typeof(ClassControl), + new PropertyMetadata(null) + ); + public object CloneButtonCommandParameter + { + get => GetValue(CloneButtonCommandParameterProperty); + set => SetValue(CloneButtonCommandParameterProperty, value); + } + + + public IProjectService ProjectService => _projectService; + + #endregion + + + + #region CONSTRUCTORS + + public ClassControl() + { + _projectService = ServiceProvider.Instance.GetService(); + _tabNavigationService = ServiceProvider.Instance.GetService(); + + InitializeComponent(); + } + + #endregion + + + + #region PRIVATE METHODS + + private void RemoveButton_Click(object sender, RoutedEventArgs e) => RemoveButtonCommand?.Execute(RemoveButtonCommandParameter); + + private void CloneButton_Click(object sender, RoutedEventArgs e) => CloneButtonCommand?.Execute(CloneButtonCommandParameter); + + #endregion + + } +} diff --git a/TimetableDesigner/Controls/DynamicGrid.xaml b/TimetableDesigner/Controls/DynamicGrid.xaml new file mode 100644 index 0000000..196cb83 --- /dev/null +++ b/TimetableDesigner/Controls/DynamicGrid.xaml @@ -0,0 +1,10 @@ + + + diff --git a/TimetableDesigner/Controls/DynamicGrid.xaml.cs b/TimetableDesigner/Controls/DynamicGrid.xaml.cs new file mode 100644 index 0000000..4ab9d81 --- /dev/null +++ b/TimetableDesigner/Controls/DynamicGrid.xaml.cs @@ -0,0 +1,602 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; +using static TimetableDesigner.ViewModels.Views.TimetableEditorViewVM; + +namespace TimetableDesigner.Controls +{ + public partial class DynamicGrid : UserControl + { + #region FIELDS + + private Border[,]? _contentCells; + private Border[]? _columnHeadersCells; + private Border[]? _rowHeadersCells; + private List _dispatcherOperations; + + #endregion + + + + #region PROPERTIES + + private static DependencyProperty ColumnsProperty = DependencyProperty.Register( + "Columns", + typeof(int), + typeof(DynamicGrid), + new PropertyMetadata(0, new PropertyChangedCallback(OnLayoutChanged)) + ); + public int Columns + { + get => (int)GetValue(ColumnsProperty); + set => SetValue(ColumnsProperty, value); + } + + private static DependencyProperty RowsProperty = DependencyProperty.Register( + "Rows", + typeof(int), + typeof(DynamicGrid), + new PropertyMetadata(0, new PropertyChangedCallback(OnLayoutChanged)) + ); + public int Rows + { + get => (int)GetValue(RowsProperty); + set => SetValue(RowsProperty, value); + } + + + private static DependencyProperty ColumnHeadersSourceProperty = DependencyProperty.Register( + "ColumnHeadersSource", + typeof(IEnumerable), + typeof(DynamicGrid), + new PropertyMetadata(new PropertyChangedCallback(OnColumnHeadersChanged)) + ); + public IEnumerable ColumnHeadersSource + { + get => (IEnumerable)GetValue(ColumnHeadersSourceProperty); + set => SetValue(ColumnHeadersSourceProperty, value); + } + + + private static DependencyProperty ColumnHeadersTemplateProperty = DependencyProperty.Register( + "ColumnHeadersTemplate", + typeof(DataTemplate), + typeof(DynamicGrid), + new PropertyMetadata(new PropertyChangedCallback(OnColumnHeadersChanged)) + ); + public DataTemplate ColumnHeadersTemplate + { + get => (DataTemplate)GetValue(ColumnHeadersTemplateProperty); + set => SetValue(ColumnHeadersTemplateProperty, value); + } + + + private static DependencyProperty RowHeadersSourceProperty = DependencyProperty.Register( + "RowHeadersSource", + typeof(IEnumerable), + typeof(DynamicGrid), + new PropertyMetadata(new PropertyChangedCallback(OnRowHeadersChanged)) + ); + public IEnumerable RowHeadersSource + { + get => (IEnumerable)GetValue(RowHeadersSourceProperty); + set => SetValue(RowHeadersSourceProperty, value); + } + + private static DependencyProperty RowHeadersTemplateProperty = DependencyProperty.Register( + "RowHeadersTemplate", + typeof(DataTemplate), + typeof(DynamicGrid), + new PropertyMetadata(new PropertyChangedCallback(OnRowHeadersChanged)) + ); + public DataTemplate RowHeadersTemplate + { + get => (DataTemplate)GetValue(RowHeadersTemplateProperty); + set => SetValue(RowHeadersTemplateProperty, value); + } + + + private static DependencyProperty ItemsSourceProperty = DependencyProperty.Register( + "ItemsSource", + typeof(IEnumerable), + typeof(DynamicGrid), + new PropertyMetadata(new PropertyChangedCallback(OnItemsChanged)) + ); + public IEnumerable ItemsSource + { + get => (IEnumerable)GetValue(ItemsSourceProperty); + set => SetValue(ItemsSourceProperty, value); + } + + private static DependencyProperty ItemTemplateProperty = DependencyProperty.Register( + "ItemTemplate", + typeof(DataTemplate), + typeof(DynamicGrid), + new PropertyMetadata(new PropertyChangedCallback(OnItemsChanged)) + ); + public DataTemplate ItemTemplate + { + get => (DataTemplate)GetValue(ItemTemplateProperty); + set => SetValue(ItemTemplateProperty, value); + } + + private static DependencyProperty CellBorderThicknessProperty = DependencyProperty.Register( + "CellBorderThickness", + typeof(Thickness), + typeof(DynamicGrid), + new PropertyMetadata(new Thickness(0), new PropertyChangedCallback(OnCellBorderThicknessChanged)) + ); + public Thickness CellBorderThickness + { + get => (Thickness)GetValue(CellBorderThicknessProperty); + set => SetValue(CellBorderThicknessProperty, value); + } + + private static DependencyProperty CellBorderBrushProperty = DependencyProperty.Register( + "CellBorderBrush", + typeof(Brush), + typeof(DynamicGrid), + new PropertyMetadata(Brushes.Black, new PropertyChangedCallback(OnCellBorderBrushChanged)) + ); + public Brush CellBorderBrush + { + get => (Brush)GetValue(CellBorderBrushProperty); + set => SetValue(CellBorderBrushProperty, value); + } + + private static DependencyProperty HeaderCellBorderThicknessProperty = DependencyProperty.Register( + "HeaderCellBorderThickness", + typeof(Thickness?), + typeof(DynamicGrid), + new PropertyMetadata(null, new PropertyChangedCallback(OnHeaderCellBorderThicknessChanged)) + ); + public Thickness HeaderCellBorderThickness + { + get + { + Thickness? headerThickness = (Thickness)GetValue(HeaderCellBorderThicknessProperty); + if (headerThickness is null) + { + return CellBorderThickness; + } + else + { + return headerThickness.Value; + } + } + set => SetValue(HeaderCellBorderThicknessProperty, value); + } + + private static DependencyProperty HeaderCellBorderBrushProperty = DependencyProperty.Register( + "HeaderCellBorderBrush", + typeof(Brush), + typeof(DynamicGrid), + new PropertyMetadata(null, new PropertyChangedCallback(OnHeaderCellBorderBrushChanged)) + ); + public Brush? HeaderCellBorderBrush + { + get + { + Brush headerBrush = (Brush)GetValue(HeaderCellBorderBrushProperty); + if (headerBrush is null) + { + headerBrush = CellBorderBrush; + } + return headerBrush; + } + set => SetValue(HeaderCellBorderBrushProperty, value); + } + + private static DependencyProperty StrechHeaderCellsToContentCellsSizeProperty = DependencyProperty.Register( + "StrechHeaderCellsToContentCellsSize", + typeof(bool), + typeof(DynamicGrid), + new PropertyMetadata(false, new PropertyChangedCallback(OnStrechHeaderCellsToContentCellsSizeChanged)) + ); + public bool StrechHeaderCellsToContentCellsSize + { + get => (bool)GetValue(StrechHeaderCellsToContentCellsSizeProperty); + set => SetValue(StrechHeaderCellsToContentCellsSizeProperty, value); + } + + #endregion + + + + #region EVENTS + + private static DependencyProperty ItemMovedCommandProperty = DependencyProperty.Register( + "ItemMovedCommand", + typeof(ICommand), + typeof(DynamicGrid), + new PropertyMetadata(null) + ); + public ICommand ItemMovedCommand + { + get => (ICommand)GetValue(ItemMovedCommandProperty); + set => SetValue(ItemMovedCommandProperty, value); + } + + private static DependencyProperty ItemMovedCommandParameterProperty = DependencyProperty.Register( + "ItemMovedCommandParameter", + typeof(object), + typeof(DynamicGrid), + new PropertyMetadata(null) + ); + public object ItemMovedCommandParameter + { + get => (object)GetValue(ItemMovedCommandParameterProperty); + set => SetValue(ItemMovedCommandParameterProperty, value); + } + + [Browsable(true)] + [Category("Action")] + [Description("Invoked when item moved to another cell")] + public event EventHandler ItemMoved; + + #endregion + + + + #region CONSTRUCTORS + + public DynamicGrid() + { + _columnHeadersCells = null; + _rowHeadersCells = null; + _contentCells = null; + _dispatcherOperations = new List(); + + InitializeComponent(); + } + + #endregion + + + + #region PRIVATE METHODS + + private void LayoutRefresh() + { + BaseGrid.Children.Clear(); + BaseGrid.ColumnDefinitions.Clear(); + BaseGrid.RowDefinitions.Clear(); + + if (Rows <= 0 || Columns <= 0) + { + _rowHeadersCells = null; + _columnHeadersCells = null; + _contentCells = null; + return; + } + else + { + _rowHeadersCells = null; + _columnHeadersCells = null; + _contentCells = new Border[Rows, Columns]; + } + + bool columnHeaders = ColumnHeadersSource != null && ColumnHeadersSource.Cast().Any(); + short columnHeadersNum = Convert.ToInt16(columnHeaders); + + bool rowHeaders = RowHeadersSource != null && RowHeadersSource.Cast().Any(); + short rowHeadersNum = Convert.ToInt16(rowHeaders); + + if (columnHeaders) + { + RowDefinition columnHeaderRow = new RowDefinition(); + if (!StrechHeaderCellsToContentCellsSize) + { + columnHeaderRow.Height = GridLength.Auto; + } + BaseGrid.RowDefinitions.Add(columnHeaderRow); + _columnHeadersCells = new Border[Columns]; + + for (int column = 0; column < Columns; column++) + { + Border border = new Border() + { + BorderBrush = HeaderCellBorderBrush, + BorderThickness = HeaderCellBorderThickness, + }; + Grid.SetColumn(border, column + rowHeadersNum); + Grid.SetRow(border, 0); + BaseGrid.Children.Add(border); + _columnHeadersCells[column] = border; + } + ColumnHeadersRefresh(); + } + + if (rowHeaders) + { + ColumnDefinition rowHeaderColumn = new ColumnDefinition(); + if (!StrechHeaderCellsToContentCellsSize) + { + rowHeaderColumn.Width = GridLength.Auto; + } + BaseGrid.ColumnDefinitions.Add(rowHeaderColumn); + _rowHeadersCells = new Border[Rows]; + + for (int row = 0; row < Rows; row++) + { + Border border = new Border() + { + BorderBrush = HeaderCellBorderBrush, + BorderThickness = HeaderCellBorderThickness, + }; + Grid.SetColumn(border, 0); + Grid.SetRow(border, row + columnHeadersNum); + BaseGrid.Children.Add(border); + _rowHeadersCells[row] = border; + } + RowHeadersRefresh(); + } + + for (int _ = 0; _ < Rows; _++) + { + BaseGrid.RowDefinitions.Add(new RowDefinition() + { + Height = GridLength.Auto, + }); + } + for (int _ = 0; _ < Columns; _++) + { + BaseGrid.ColumnDefinitions.Add(new ColumnDefinition() + { + Width = GridLength.Auto, + }); + } + + for (int row = 0; row < Rows; row++) + { + for (int column = 0; column < Columns; column++) + { + Border border = new Border() + { + BorderBrush = CellBorderBrush, + BorderThickness = CellBorderThickness, + }; + Grid.SetColumn(border, column + rowHeadersNum); + Grid.SetRow(border, row + columnHeadersNum); + + UniformGrid grid = new UniformGrid() + { + Rows = 1, + AllowDrop = true, + Background = Brushes.Transparent, + }; + grid.Drop += OnItemMoved; + + border.Child = grid; + + BaseGrid.Children.Add(border); + _contentCells[row, column] = border; + } + } + ItemsRefresh(); + } + + private void ColumnHeadersRefresh(bool switched = false) => HeadersRefresh(switched, ColumnHeadersSource, _columnHeadersCells, ColumnHeadersTemplate); + private void RowHeadersRefresh(bool switched = false) => HeadersRefresh(switched, RowHeadersSource, _rowHeadersCells, RowHeadersTemplate); + private void HeadersRefresh(bool switched, IEnumerable headerSource, Border[]? headerCells, DataTemplate headerTemplate) + { + if (switched) + { + LayoutRefresh(); + } + + if (headerSource is not null && headerCells is not null) + { + IEnumerable headersSourceCollection = headerSource.Cast(); + for (int index = 0; index < headerCells.Length; index++) + { + headerCells[index].Child = null; + if (index < headersSourceCollection.Count()) + { + object headerItem = headersSourceCollection.ElementAt(index); + + FrameworkElement? control = null; + if (headerTemplate is not null) + { + control = headerTemplate.LoadContent() as FrameworkElement; + if (control is not null) + { + control.DataContext = headerItem; + } + } + control ??= new Label() + { + Content = headerItem.ToString() + }; + + headerCells[index].Child = control; + } + } + } + } + + private void CellBorderRefresh(CellBorderRefreshProperty mode, CellBorderRefreshType type) + { + if ((type & CellBorderRefreshType.Headers) == CellBorderRefreshType.Headers) + { + List> headerCellsCollection = new List>(); + if (_columnHeadersCells is not null) headerCellsCollection.Add(_columnHeadersCells); + if (_rowHeadersCells is not null) headerCellsCollection.Add(_rowHeadersCells); + foreach (IEnumerable cells in headerCellsCollection) + { + foreach (Border cell in cells) + { + if ((mode & CellBorderRefreshProperty.Thickness) == CellBorderRefreshProperty.Thickness) cell.BorderThickness = HeaderCellBorderThickness; + if ((mode & CellBorderRefreshProperty.Brush) == CellBorderRefreshProperty.Brush) cell.BorderBrush = HeaderCellBorderBrush; + } + } + } + if ((type & CellBorderRefreshType.Content) == CellBorderRefreshType.Content && _contentCells is not null) + { + foreach (Border cell in _contentCells) + { + if ((mode & CellBorderRefreshProperty.Thickness) == CellBorderRefreshProperty.Thickness) cell.BorderThickness = CellBorderThickness; + if ((mode & CellBorderRefreshProperty.Brush) == CellBorderRefreshProperty.Brush) cell.BorderBrush = CellBorderBrush; + } + } + } + + private void ItemsRefresh() + { + if (ItemsSource is not null && _contentCells is not null) + { + foreach (DispatcherOperation operation in _dispatcherOperations) + { + operation.Abort(); + } + _dispatcherOperations.Clear(); + + foreach (Border cell in _contentCells) + { + ((UniformGrid)cell.Child).Children.Clear(); + } + + IEnumerable itemsSourceCollection = ItemsSource.Cast(); + foreach (object item in itemsSourceCollection) + { + FrameworkElement? control = null; + if (ItemTemplate is not null) + { + control = ItemTemplate.LoadContent() as FrameworkElement; + if (control is not null) + { + control.DataContext = item; + } + } + control ??= new Label() + { + Content = item.ToString() + }; + + DispatcherOperation operation = Dispatcher.BeginInvoke(() => + { + int row = GetRow(control); + int column = GetColumn(control); + Border cell = _contentCells[row, column]; + UniformGrid cellGrid = (UniformGrid)cell.Child; + if (!cellGrid.Children.Contains(control)) + { + cellGrid.Children.Add(control); + } + }, DispatcherPriority.Loaded); + _dispatcherOperations.Add(operation); + } + } + } + + private void OnItemMoved(object sender, DragEventArgs e) + { + UniformGrid? target = sender as UniformGrid; + object item = e.Data.GetData(DataFormats.Serializable); + if (target is not null && item is UIElement element && _contentCells is not null) + { + int x = 0; + int y = 0; + for (x = 0; x < _contentCells.GetLength(0); x++) + { + for (y = 0; y < _contentCells.GetLength(1); y++) + { + if (_contentCells[x, y].Child.Equals(target)) + { + goto LoopEnd; + } + } + } + LoopEnd: + if (x != GetRow(element) || y != GetColumn(element)) + { + SetRow(element, x); + SetColumn(element, y); + ItemMoved?.Invoke(this, EventArgs.Empty); + ItemMovedCommand?.Execute(ItemMovedCommandParameter); + } + } + } + + private void HeaderRowColumnSizeRefresh() + { + GridLength size; + if (StrechHeaderCellsToContentCellsSize) + { + size = new GridLength(1, GridUnitType.Star); + } + else + { + size = GridLength.Auto; + } + + if (BaseGrid.RowDefinitions.Count > 1 && ColumnHeadersSource is not null) + { + BaseGrid.RowDefinitions[0].Height = size; + } + if (BaseGrid.ColumnDefinitions.Count > 1 && RowHeadersSource is not null) + { + BaseGrid.ColumnDefinitions[0].Width = size; + } + } + + private static void OnLayoutChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => ((DynamicGrid)obj).LayoutRefresh(); + private static void OnColumnHeadersChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => ((DynamicGrid)obj).ColumnHeadersRefresh((args.OldValue is null && args.NewValue is not null) || (args.OldValue is not null && args.NewValue is null)); + private static void OnRowHeadersChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => ((DynamicGrid)obj).RowHeadersRefresh((args.OldValue is null && args.NewValue is not null) || (args.OldValue is not null && args.NewValue is null)); + private static void OnItemsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => ((DynamicGrid)obj).ItemsRefresh(); + private static void OnCellBorderThicknessChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => ((DynamicGrid)obj).CellBorderRefresh(CellBorderRefreshProperty.Thickness, obj.GetValue(HeaderCellBorderThicknessProperty) is null ? CellBorderRefreshType.Headers | CellBorderRefreshType.Content : CellBorderRefreshType.Content); + private static void OnCellBorderBrushChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => ((DynamicGrid)obj).CellBorderRefresh(CellBorderRefreshProperty.Brush, obj.GetValue(HeaderCellBorderBrushProperty) is null ? CellBorderRefreshType.Headers | CellBorderRefreshType.Content : CellBorderRefreshType.Content); + private static void OnHeaderCellBorderThicknessChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => ((DynamicGrid)obj).CellBorderRefresh(CellBorderRefreshProperty.Thickness, CellBorderRefreshType.Headers); + private static void OnHeaderCellBorderBrushChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => ((DynamicGrid)obj).CellBorderRefresh(CellBorderRefreshProperty.Brush, CellBorderRefreshType.Headers); + private static void OnStrechHeaderCellsToContentCellsSizeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => ((DynamicGrid)obj).HeaderRowColumnSizeRefresh(); + + #endregion + + + + #region COLUMN/ROW SET/GET STATIC METHODS + + private static DependencyProperty ColumnProperty = DependencyProperty.RegisterAttached("Column", typeof(int), typeof(DynamicGrid), new PropertyMetadata(0)); + public static int GetColumn(UIElement element) => (int)element.GetValue(ColumnProperty); + public static void SetColumn(UIElement element, int value) => element.SetValue(ColumnProperty, value); + + private static DependencyProperty RowProperty = DependencyProperty.RegisterAttached("Row", typeof(int), typeof(DynamicGrid), new PropertyMetadata(0)); + public static int GetRow(UIElement element) => (int)element.GetValue(RowProperty); + public static void SetRow(UIElement element, int value) => element.SetValue(RowProperty, value); + + #endregion + + + + #region ENUMS + + [Flags] + private enum CellBorderRefreshProperty + { + Brush = 1, + Thickness = 2, + } + + [Flags] + private enum CellBorderRefreshType + { + Headers = 1, + Content = 2, + } + + #endregion + } +} diff --git a/TimetableDesigner/Controls/TimetableEditorControl.xaml b/TimetableDesigner/Controls/TimetableEditorControl.xaml deleted file mode 100644 index 52d4b1e..0000000 --- a/TimetableDesigner/Controls/TimetableEditorControl.xaml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TimetableDesigner/Controls/TimetableEditorControl.xaml.cs b/TimetableDesigner/Controls/TimetableEditorControl.xaml.cs deleted file mode 100644 index 83bed66..0000000 --- a/TimetableDesigner/Controls/TimetableEditorControl.xaml.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; -using TimetableDesigner.Services; -using TimetableDesigner.Services.Project; -using TimetableDesigner.ViewModels.Models; - -namespace TimetableDesigner.Controls -{ - public partial class TimetableEditorControl : UserControl - { - #region FIELDS - - private IProjectService _projectService; - - #endregion - - - - #region PROPERTIES - - public IGroupViewModel Group - { - get => (IGroupViewModel)GetValue(GroupProperty); - set => SetValue(GroupProperty, value); - } - public static readonly DependencyProperty GroupProperty = DependencyProperty.Register("Group", typeof(IGroupViewModel), typeof(TimetableEditorControl)); - - #endregion - - - - #region CONSTRUCTORS - - public TimetableEditorControl() - { - _projectService = ServiceProvider.Instance.GetService(); - - InitializeComponent(); - } - - #endregion - } -} diff --git a/TimetableDesigner/Converters/BooleanToGridColumnRowVisibilityConverter.cs b/TimetableDesigner/Converters/BooleanToGridColumnRowVisibilityConverter.cs new file mode 100644 index 0000000..00bd265 --- /dev/null +++ b/TimetableDesigner/Converters/BooleanToGridColumnRowVisibilityConverter.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace TimetableDesigner.Converters +{ + class BooleanToGridColumnRowVisibilityConverter : IValueConverter + { + private static GridLength _notVisible = new GridLength(0, GridUnitType.Pixel); + private static GridLength _visible = new GridLength(1, GridUnitType.Star); + + // (bool)parameter - reverse output + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is null || value.GetType() != typeof(bool)) + { + return new GridLength(1, GridUnitType.Star); + } + bool isVisible = (bool)value; + + bool reverse = false; + if (parameter is not null && parameter.GetType() == typeof(bool)) + { + reverse = (bool)parameter; + } + + return isVisible ^ reverse ? _visible : _notVisible; + } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is null || value.GetType() != typeof(GridLength)) + { + return true; + } + GridLength visibility = (GridLength)value; + + bool reverse = false; + if (parameter is not null && parameter.GetType() == typeof(bool)) + { + reverse = (bool)parameter; + } + + return visibility != _notVisible ^ reverse; + } + } +} diff --git a/TimetableDesigner/Converters/BooleanToVisibilityConverter.cs b/TimetableDesigner/Converters/BooleanToVisibilityConverter.cs new file mode 100644 index 0000000..c57cdf1 --- /dev/null +++ b/TimetableDesigner/Converters/BooleanToVisibilityConverter.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace TimetableDesigner.Converters +{ + public class BooleanToVisibilityConverter : IValueConverter + { + // (bool)parameter - reverse output + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is null || value.GetType() != typeof(bool)) + { + return Visibility.Visible; + } + bool isVisible = (bool)value; + + bool reverse = false; + if (parameter is not null && parameter.GetType() == typeof(bool)) + { + reverse = (bool)parameter; + } + + return isVisible ^ reverse ? Visibility.Visible : Visibility.Collapsed; + } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is null || value.GetType() != typeof(Visibility)) + { + return true; + } + Visibility visibility = (Visibility)value; + + bool reverse = false; + if (parameter is not null && parameter.GetType() == typeof(bool)) + { + reverse = (bool)parameter; + } + + return visibility == Visibility.Visible ^ reverse; + } + } +} diff --git a/TimetableDesigner/Converters/ByteArrayToColorConverter.cs b/TimetableDesigner/Converters/ByteArrayToColorConverter.cs new file mode 100644 index 0000000..08d4563 --- /dev/null +++ b/TimetableDesigner/Converters/ByteArrayToColorConverter.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Windows.Media; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using System.Diagnostics; + +namespace TimetableDesigner.Converters +{ + public class ByteArrayToColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + byte[] bytes = (byte[])value; + Color color; + switch (bytes.Length) + { + case < 3: color = Colors.Red; break; + case 3: color = Color.FromRgb(bytes[0], bytes[1], bytes[2]); break; + case > 4: color = Color.FromArgb(bytes[0], bytes[1], bytes[2], bytes[3]); break; + } + return color; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + Color color = (Color)value; + List bytes = new List(); + + if (color.A != 0xFF) bytes.Add(color.A); + bytes.Add(color.R); + bytes.Add(color.G); + bytes.Add(color.B); + + return bytes.ToArray(); + } + } +} diff --git a/TimetableDesigner/Converters/ColorBrightnessIsHigherToBooleanConverter.cs b/TimetableDesigner/Converters/ColorBrightnessIsHigherToBooleanConverter.cs new file mode 100644 index 0000000..a08ef5a --- /dev/null +++ b/TimetableDesigner/Converters/ColorBrightnessIsHigherToBooleanConverter.cs @@ -0,0 +1,39 @@ +using ControlzEx.Standard; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; + +namespace TimetableDesigner.Converters +{ + public class ColorBrightnessIsHigherToBooleanConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is byte[] color && (parameter is int min || (parameter is string minStr && int.TryParse(minStr, out min)))) + { + int start = 0; + if (color.Length == 4) + { + start = 1; + } + int result = (int)Math.Sqrt(color[start] * color[start] * .241 + + color[start + 1] * color[start + 1] * .691 + + color[start + 2] * color[start + 2] * .068); + return min < result; + } + return false; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/TimetableDesigner/Converters/IUnitVMToUnitNameConverter.cs b/TimetableDesigner/Converters/IUnitVMToUnitNameConverter.cs new file mode 100644 index 0000000..f3bb424 --- /dev/null +++ b/TimetableDesigner/Converters/IUnitVMToUnitNameConverter.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using TimetableDesigner.Core; +using TimetableDesigner.Properties; +using TimetableDesigner.ViewModels.Models; +using TimetableDesigner.ViewModels.Models.Base; + +namespace TimetableDesigner.Converters +{ + internal class IUnitVMToUnitNameConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is IUnitVM unit) + { + switch (unit) + { + case ClassroomVM classroom: return Resources.Global_Classroom; + case TeacherVM teacher: return Resources.Global_Teacher; + case GroupVM group: return Resources.Global_Group; + case SubgroupVM subgroup: return Resources.Global_Subgroup; + } + } + return string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/TimetableDesigner/Converters/IntegerAddConstantConverter.cs b/TimetableDesigner/Converters/IntegerAddConstantConverter.cs index b0d0637..8c0bf2c 100644 --- a/TimetableDesigner/Converters/IntegerAddConstantConverter.cs +++ b/TimetableDesigner/Converters/IntegerAddConstantConverter.cs @@ -12,7 +12,7 @@ namespace TimetableDesigner.Converters { #region PUBLIC METHODS - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => (int)value + (int)parameter; + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => (int)value + int.Parse((string)parameter); public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => (int)value - (int)parameter; diff --git a/TimetableDesigner/Converters/TimetableDayToColumnNumberConverter.cs b/TimetableDesigner/Converters/TimetableDayToColumnNumberConverter.cs new file mode 100644 index 0000000..98b8dfb --- /dev/null +++ b/TimetableDesigner/Converters/TimetableDayToColumnNumberConverter.cs @@ -0,0 +1,60 @@ +using System; +using System.CodeDom; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using TimetableDesigner.Core; +using TimetableDesigner.Services; +using TimetableDesigner.Services.Project; +using TimetableDesigner.ViewModels.Models; + +namespace TimetableDesigner.Converters +{ + internal class TimetableDayToColumnNumberConverter : IValueConverter + { + #region FIELDS + + private readonly IProjectService _projectService; + + #endregion + + + + #region CONSTRUCTORS + + public TimetableDayToColumnNumberConverter() + { + _projectService = ServiceProvider.Instance.GetService(); + } + + #endregion + + + + #region PUBLIC METHODS + + //value - TimetableDay + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is TimetableDay day) + { + return _projectService.ProjectViewModel.TimetableTemplate.Days.IndexOf(day); + } + return 0; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is int index && index >= 0) + { + return _projectService.ProjectViewModel.TimetableTemplate.Days[index]; + } + return null; + } + + #endregion + } +} diff --git a/TimetableDesigner/Converters/TimetableSlotToRowNumberConverter.cs b/TimetableDesigner/Converters/TimetableSlotToRowNumberConverter.cs new file mode 100644 index 0000000..d5e4141 --- /dev/null +++ b/TimetableDesigner/Converters/TimetableSlotToRowNumberConverter.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using TimetableDesigner.Core; +using TimetableDesigner.Services.Project; +using TimetableDesigner.Services; +using System.Diagnostics; + +namespace TimetableDesigner.Converters +{ + internal class TimetableSlotToRowNumberConverter : IValueConverter + { + #region FIELDS + + private readonly IProjectService _projectService; + + #endregion + + + + #region CONSTRUCTORS + + public TimetableSlotToRowNumberConverter() + { + _projectService = ServiceProvider.Instance.GetService(); + } + + #endregion + + + + #region PUBLIC METHODS + + //value - TimetableSlot + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is TimetableSpan slot) + { + return _projectService.ProjectViewModel.TimetableTemplate.Slots.IndexOf(slot); + } + return 0; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is int index && index >= 0) + { + return _projectService.ProjectViewModel.TimetableTemplate.Slots[index]; + } + return null; + } + + #endregion + } +} diff --git a/TimetableDesigner/Converters/ViewModelToViewConverter.cs b/TimetableDesigner/Converters/ViewModelToViewConverter.cs index de6717c..41d19b7 100644 --- a/TimetableDesigner/Converters/ViewModelToViewConverter.cs +++ b/TimetableDesigner/Converters/ViewModelToViewConverter.cs @@ -19,12 +19,13 @@ namespace TimetableDesigner.Converters private static readonly Dictionary _viewModelViewPairs = new Dictionary { - { typeof(MainViewModel), typeof(MainWindow) }, - { typeof(WelcomeViewModel), typeof(WelcomeView) }, - { typeof(ProjectSettingsViewModel), typeof(ProjectSettingsView) }, - { typeof(ClassroomEditViewModel), typeof(ClassroomEditView) }, - { typeof(TeacherEditViewModel), typeof(TeacherEditView) }, - { typeof(GroupEditViewModel), typeof(GroupEditView) }, + { typeof(MainWindowVM), typeof(MainWindow) }, + { typeof(WelcomeViewVM), typeof(WelcomeView) }, + { typeof(ProjectSettingsViewVM), typeof(ProjectSettingsView) }, + { typeof(ClassroomEditorViewVM), typeof(ClassroomEditorView) }, + { typeof(TeacherEditorViewVM), typeof(TeacherEditorView) }, + { typeof(GroupEditorViewVM), typeof(GroupEditorView) }, + { typeof(TimetableEditorViewVM), typeof(TimetableEditorView) }, }; #endregion @@ -35,7 +36,7 @@ namespace TimetableDesigner.Converters public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - BaseViewViewModel? viewModel = value as BaseViewViewModel; + IViewVM? viewModel = value as IViewVM; if (viewModel is not null) { Type view = _viewModelViewPairs[viewModel.GetType()]; diff --git a/TimetableDesigner/Globals/Path.cs b/TimetableDesigner/Globals/Path.cs new file mode 100644 index 0000000..6fc3a06 --- /dev/null +++ b/TimetableDesigner/Globals/Path.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TimetableDesigner.Globals +{ + public static class Path + { + public static readonly string ApplicationData = $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}\\TimetableDesigner"; + public static readonly string RecentProjectsFile = $"{ApplicationData}\\recent"; + } +} diff --git a/TimetableDesigner/Properties/Resources.Designer.cs b/TimetableDesigner/Properties/Resources.Designer.cs index b16c91b..d2675f0 100644 --- a/TimetableDesigner/Properties/Resources.Designer.cs +++ b/TimetableDesigner/Properties/Resources.Designer.cs @@ -114,6 +114,114 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to During this class, other classes are taking place in this classroom. + /// + public static string Errors_ClassroomBusy { + get { + return ResourceManager.GetString("Errors.ClassroomBusy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Class has no assigned classroom. + /// + public static string Errors_ClassroomNotAssigned { + get { + return ResourceManager.GetString("Errors.ClassroomNotAssigned", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to During this class, group, to which selected subgroup is assigned, already has other classes. + /// + public static string Errors_GroupAllBusy { + get { + return ResourceManager.GetString("Errors.GroupAllBusy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to During this class, the selected group already has other classes. + /// + public static string Errors_GroupBusy { + get { + return ResourceManager.GetString("Errors.GroupBusy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Class has no assigned group. + /// + public static string Errors_GroupNotAssigned { + get { + return ResourceManager.GetString("Errors.GroupNotAssigned", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to During this class, part of selected group already has other classes in subgroup. + /// + public static string Errors_GroupPartBusy { + get { + return ResourceManager.GetString("Errors.GroupPartBusy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Class is not scheduled. + /// + public static string Errors_SlotNotAssigned { + get { + return ResourceManager.GetString("Errors.SlotNotAssigned", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to During this class, the selected teacher conducts other classes. + /// + public static string Errors_TeacherBusy { + get { + return ResourceManager.GetString("Errors.TeacherBusy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Class has no assigned teacher. + /// + public static string Errors_TeacherNotAssigned { + get { + return ResourceManager.GetString("Errors.TeacherNotAssigned", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Selected teacher is not available during the class. + /// + public static string Errors_TeacherNotAvailable { + get { + return ResourceManager.GetString("Errors.TeacherNotAvailable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All files. + /// + public static string Global_AllFiles { + get { + return ResourceManager.GetString("Global.AllFiles", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Classroom. + /// + public static string Global_Classroom { + get { + return ResourceManager.GetString("Global.Classroom", resourceCulture); + } + } + /// /// Looks up a localized string similar to New classroom. /// @@ -150,6 +258,15 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to Group. + /// + public static string Global_Group { + get { + return ResourceManager.GetString("Global.Group", resourceCulture); + } + } + /// /// Looks up a localized string similar to no project loaded. /// @@ -159,6 +276,33 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to TimetableEditor project file. + /// + public static string Global_ProjectFiletypeDescription { + get { + return ResourceManager.GetString("Global.ProjectFiletypeDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Subgroup. + /// + public static string Global_Subgroup { + get { + return ResourceManager.GetString("Global.Subgroup", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Teacher. + /// + public static string Global_Teacher { + get { + return ResourceManager.GetString("Global.Teacher", resourceCulture); + } + } + /// /// Looks up a localized string similar to Delete. /// @@ -231,6 +375,51 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to Class name. + /// + public static string Main_Errors_ClassName { + get { + return ResourceManager.GetString("Main.Errors.ClassName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Day. + /// + public static string Main_Errors_Day { + get { + return ResourceManager.GetString("Main.Errors.Day", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Description. + /// + public static string Main_Errors_Description { + get { + return ResourceManager.GetString("Main.Errors.Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Slot. + /// + public static string Main_Errors_Slot { + get { + return ResourceManager.GetString("Main.Errors.Slot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Source. + /// + public static string Main_Errors_Source { + get { + return ResourceManager.GetString("Main.Errors.Source", resourceCulture); + } + } + /// /// Looks up a localized string similar to Project is loaded. Do you want to save?. /// @@ -240,6 +429,97 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to Edit. + /// + public static string Main_Ribbon_Edit { + get { + return ResourceManager.GetString("Main.Ribbon.Edit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Timetable. + /// + public static string Main_Ribbon_Edit_Timetable { + get { + return ResourceManager.GetString("Main.Ribbon.Edit.Timetable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Autoschedule all unscheduled classes. + /// + public static string Main_Ribbon_Edit_Timetable_Autoschedule { + get { + return ResourceManager.GetString("Main.Ribbon.Edit.Timetable.Autoschedule", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Following classes could not be scheduled. + /// + public static string Main_Ribbon_Edit_Timetable_Autoschedule_Message_FollowingClassesCouldNotBeScheduled { + get { + return ResourceManager.GetString("Main.Ribbon.Edit.Timetable.Autoschedule.Message.FollowingClassesCouldNotBeSchedul" + + "ed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to more. + /// + public static string Main_Ribbon_Edit_Timetable_Autoschedule_Message_More { + get { + return ResourceManager.GetString("Main.Ribbon.Edit.Timetable.Autoschedule.Message.More", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There is no unscheduled classes. + /// + public static string Main_Ribbon_Edit_Timetable_Autoschedule_Message_NoUnscheduledClasses { + get { + return ResourceManager.GetString("Main.Ribbon.Edit.Timetable.Autoschedule.Message.NoUnscheduledClasses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Successfully scheduled classes. + /// + public static string Main_Ribbon_Edit_Timetable_Autoschedule_Message_SuccessfullyScheduled { + get { + return ResourceManager.GetString("Main.Ribbon.Edit.Timetable.Autoschedule.Message.SuccessfullyScheduled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Classroom. + /// + public static string Main_Ribbon_Edit_Timetable_Autoschedule_Message_UnitClassroom { + get { + return ResourceManager.GetString("Main.Ribbon.Edit.Timetable.Autoschedule.Message.UnitClassroom", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Group. + /// + public static string Main_Ribbon_Edit_Timetable_Autoschedule_Message_UnitGroup { + get { + return ResourceManager.GetString("Main.Ribbon.Edit.Timetable.Autoschedule.Message.UnitGroup", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Teacher. + /// + public static string Main_Ribbon_Edit_Timetable_Autoschedule_Message_UnitTeacher { + get { + return ResourceManager.GetString("Main.Ribbon.Edit.Timetable.Autoschedule.Message.UnitTeacher", resourceCulture); + } + } + /// /// Looks up a localized string similar to Export. /// @@ -286,38 +566,47 @@ namespace TimetableDesigner.Properties { } /// - /// Looks up a localized string similar to New/Open. + /// Looks up a localized string similar to New. /// - public static string Main_Ribbon_File_NewOpen { + public static string Main_Ribbon_File_New { get { - return ResourceManager.GetString("Main.Ribbon.File.NewOpen", resourceCulture); + return ResourceManager.GetString("Main.Ribbon.File.New", resourceCulture); } } /// /// Looks up a localized string similar to New. /// - public static string Main_Ribbon_File_NewOpen_New { + public static string Main_Ribbon_File_New_New { get { - return ResourceManager.GetString("Main.Ribbon.File.NewOpen.New", resourceCulture); + return ResourceManager.GetString("Main.Ribbon.File.New.New", resourceCulture); } } /// /// Looks up a localized string similar to Open. /// - public static string Main_Ribbon_File_NewOpen_Open { + public static string Main_Ribbon_File_Open { get { - return ResourceManager.GetString("Main.Ribbon.File.NewOpen.Open", resourceCulture); + return ResourceManager.GetString("Main.Ribbon.File.Open", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open. + /// + public static string Main_Ribbon_File_Open_Open { + get { + return ResourceManager.GetString("Main.Ribbon.File.Open.Open", resourceCulture); } } /// /// Looks up a localized string similar to Open recent. /// - public static string Main_Ribbon_File_NewOpen_OpenRecent { + public static string Main_Ribbon_File_Open_OpenRecent { get { - return ResourceManager.GetString("Main.Ribbon.File.NewOpen.OpenRecent", resourceCulture); + return ResourceManager.GetString("Main.Ribbon.File.Open.OpenRecent", resourceCulture); } } @@ -483,6 +772,15 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to Are you sure you want to delete this classroom. This operation is irreversible. Classroom will be unassigned for all classes.. + /// + public static string Main_Treeview_Classrooms_Message_Remove { + get { + return ResourceManager.GetString("Main.Treeview.Classrooms.Message.Remove", resourceCulture); + } + } + /// /// Looks up a localized string similar to Edit timetable. /// @@ -520,7 +818,16 @@ namespace TimetableDesigner.Properties { } /// - /// Looks up a localized string similar to Are you sure you want to delete this subgroup. This operation is irreversible. Subgroup will be unassigned for all groups.. + /// Looks up a localized string similar to Are you sure you want to delete this group. This operation is irreversible. Group will be unassigned for all classes.. + /// + public static string Main_Treeview_Groups_Message_Remove { + get { + return ResourceManager.GetString("Main.Treeview.Groups.Message.Remove", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Are you sure you want to delete this subgroup. This operation is irreversible. Subgroup will be unassigned for all groups and classes.. /// public static string Main_Treeview_Subgroups_Message_Remove { get { @@ -546,6 +853,15 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to Are you sure you want to delete this teacher. This operation is irreversible. Teacher will be unassigned for all classes.. + /// + public static string Main_Treeview_Teachers_Message_Remove { + get { + return ResourceManager.GetString("Main.Treeview.Teachers.Message.Remove", resourceCulture); + } + } + /// /// Looks up a localized string similar to Error. /// @@ -555,6 +871,15 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to Information. + /// + public static string MessageBox_Information { + get { + return ResourceManager.GetString("MessageBox.Information", resourceCulture); + } + } + /// /// Looks up a localized string similar to Question. /// @@ -564,6 +889,15 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to Warning. + /// + public static string MessageBox_Warning { + get { + return ResourceManager.GetString("MessageBox.Warning", resourceCulture); + } + } + /// /// Looks up a localized string similar to Author. /// @@ -681,6 +1015,15 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to Timetable editing. + /// + public static string Tabs_TimetableEdit { + get { + return ResourceManager.GetString("Tabs.TimetableEdit", resourceCulture); + } + } + /// /// Looks up a localized string similar to Welcome. /// @@ -761,5 +1104,23 @@ namespace TimetableDesigner.Properties { return ResourceManager.GetString("TeacherEdit.Name", resourceCulture); } } + + /// + /// Looks up a localized string similar to Autoschedule. + /// + public static string TimetableEditor_Autoschedule { + get { + return ResourceManager.GetString("TimetableEditor.Autoschedule", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unscheduled. + /// + public static string TimetableEditor_Unscheduled { + get { + return ResourceManager.GetString("TimetableEditor.Unscheduled", resourceCulture); + } + } } } diff --git a/TimetableDesigner/Properties/Resources.resx b/TimetableDesigner/Properties/Resources.resx index 186698c..c0c2017 100644 --- a/TimetableDesigner/Properties/Resources.resx +++ b/TimetableDesigner/Properties/Resources.resx @@ -135,6 +135,42 @@ Name + + During this class, other classes are taking place in this classroom + + + Class has no assigned classroom + + + During this class, group, to which selected subgroup is assigned, already has other classes + + + During this class, the selected group already has other classes + + + Class has no assigned group + + + During this class, part of selected group already has other classes in subgroup + + + Class is not scheduled + + + During this class, the selected teacher conducts other classes + + + Class has no assigned teacher + + + Selected teacher is not available during the class + + + All files + + + Classroom + New classroom @@ -147,9 +183,21 @@ New teacher + + Group + no project loaded + + TimetableEditor project file + + + Subgroup + + + Teacher + Delete @@ -174,9 +222,54 @@ Subgroups + + Class name + + + Day + + + Description + + + Slot + + + Source + Project is loaded. Do you want to save? + + Edit + + + Timetable + + + Autoschedule all unscheduled classes + + + Following classes could not be scheduled + + + more + + + There is no unscheduled classes + + + Successfully scheduled classes + + + Classroom + + + Group + + + Teacher + Export @@ -192,16 +285,19 @@ File - - New/Open - - + New - + + New + + Open - + + Open + + Open recent @@ -258,6 +354,9 @@ Edit classroom + + Are you sure you want to delete this classroom. This operation is irreversible. Classroom will be unassigned for all classes. + Edit timetable @@ -270,8 +369,11 @@ Edit group + + Are you sure you want to delete this group. This operation is irreversible. Group will be unassigned for all classes. + - Are you sure you want to delete this subgroup. This operation is irreversible. Subgroup will be unassigned for all groups. + Are you sure you want to delete this subgroup. This operation is irreversible. Subgroup will be unassigned for all groups and classes. Teachers @@ -279,12 +381,21 @@ Edit teacher + + Are you sure you want to delete this teacher. This operation is irreversible. Teacher will be unassigned for all classes. + Error + + Information + Question + + Warning + Author @@ -324,6 +435,9 @@ Teacher editing + + Timetable editing + Welcome @@ -351,4 +465,10 @@ Name + + Autoschedule + + + Unscheduled + \ No newline at end of file diff --git a/TimetableDesigner/Properties/Settings.Designer.cs b/TimetableDesigner/Properties/Settings.Designer.cs new file mode 100644 index 0000000..6a52d03 --- /dev/null +++ b/TimetableDesigner/Properties/Settings.Designer.cs @@ -0,0 +1,38 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace TimetableDesigner.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.4.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("10")] + public uint RecentProjectsCount { + get { + return ((uint)(this["RecentProjectsCount"])); + } + set { + this["RecentProjectsCount"] = value; + } + } + } +} diff --git a/TimetableDesigner/Properties/Settings.settings b/TimetableDesigner/Properties/Settings.settings new file mode 100644 index 0000000..a20a2b8 --- /dev/null +++ b/TimetableDesigner/Properties/Settings.settings @@ -0,0 +1,9 @@ + + + + + + 10 + + + \ No newline at end of file diff --git a/TimetableDesigner/Resources/Converters.xaml b/TimetableDesigner/Resources/Converters.xaml index 5007b48..7b4521a 100644 --- a/TimetableDesigner/Resources/Converters.xaml +++ b/TimetableDesigner/Resources/Converters.xaml @@ -5,4 +5,10 @@ + + + + + + \ No newline at end of file diff --git a/TimetableDesigner/Resources/Images.xaml b/TimetableDesigner/Resources/Images.xaml index e57d965..fb07e2d 100644 --- a/TimetableDesigner/Resources/Images.xaml +++ b/TimetableDesigner/Resources/Images.xaml @@ -2,8 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"> + - @@ -18,4 +18,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TimetableDesigner/Resources/Images/Autoschedule.png b/TimetableDesigner/Resources/Images/Autoschedule.png new file mode 100644 index 0000000000000000000000000000000000000000..bfdad3a5cfa2bf6cab7cdb47d252cbb5acaac6a2 GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DX&fq~K4 z)5S5QV$R!{d;N|W2(`OTcG&y#9 z76?@@S-EY4z3XP>qvuqC;A>EL8~4|WF#eV;5^?&^E%JF5$mK88U$5G7{IVg#3&8{I ztOZ5J&AaO?3e4u#o>edBo^x-Xyv6?bEoS0k3#xuzdb+2o;^nqyMkNeiBiuL=ZuGM$ z%>F0$NU7lJVRd__6X&L~a^!xDILfZjb~pZe$#q*r9f#NxyZCnU$Q{_wCzN0<)vzbc zzx=N7AtsjXiNf*?Qf9et3%99HVNGy8(H7Irr_h#L!;lmKG*^1JYD@<);T3K0RU$Aov;7^ literal 0 HcmV?d00001 diff --git a/TimetableDesigner/Resources/Images/ClassroomBlack.png b/TimetableDesigner/Resources/Images/ClassroomBlack.png new file mode 100644 index 0000000000000000000000000000000000000000..072425b9838d747624bcc9ba1924534e253cf194 GIT binary patch literal 616 zcmV-u0+;=XP)zjw6SyWjy7%tJO=Lbeuw3?>?>jSd z=bSr3#{Y?Z7{yCW#qZ{`WsS8sfjgK((ZU|$6xLft*pKsglMvm?-r*V!^h)#woW^8A z;17)9XxLahYxUpBh}Dd_xdO|fntCy2k* zAf6wH&xx2%BiSu=MXwG)*YPDm@+3$$*CoNm7@S85js|YU=CG!nfIC9ei$uWpkyJCe zZes{nFq7bZ70Yu^D!@6+CY&d568)AQ{lPVn&@vl!WzmvZxJyjq0tWRCgP~!1fwu3V z`8Qm~Cye7b`Ya#xg{JY)c&XQdGTOzFsFaO1TZ9jIfHPQEigOHi@wP&{(FpG0PQ<60 zvUcoQyo6Ufh*5-wT1lk(oGRAt%oTsJ=B!pmnY`Ifw1e$YSz9fYi`Z6ye_%noQW<6P zsyK+BCI5SM1n5jdXU@1?@?TUrS3&}#CI69?n7~L*K%N143CS<9ybBe18C^d40m`g)GeFC;W`P-Cvm@v=Q~xb z5!|)K*TwF{jpb|XvO=&o?#*VxZv5$HB_kt4ZTtcsD;k6wg^bGp0000yOaP$ZZLnrLH6v9U;*A{P*gXm6!; zs6@Zt(C~!b@j({1Uy;!DG zzyPpfFn9{g16^{>%XeX{$gHI9!gz;_+#iFP9hr@$kk|&`1F&g`JqG50*T91$P1ni@ zts8_-Y=yOf_-DxZQ;L1!YPv!U1;(-lksl&b?{5U(UK=synLJs;T4e%w zYN21J#h$nnaD_u`Cvc{v_-buH#q$s^I3K^Lr5G;90*18|gXLJjpf5nf&wwqyg_OUE zud(hQ-$Hq`G4!jDHGUORzJ;!_Zy?`7muO??SD~_8g|b?F-CY+z0{@AV*p;A|yAtV` z`$9>P1KwB{pcU~w!TlW@vZC!~-WA}v;ly$uXjbI_&?3iE8M$VSCo3M0N5$a+og#f)AdSMUfqMAjM?xj=5A z*?0&sj)5$)O0o(VXc_20F-#UtS2s4+gWmMwSN-a$fhjNy!^nv~Fa{RD8aM(@;cS2z z(2JG$B`^UFN(rAk@4%II@^w|-A}8*|nQK?Rnfxm-0x1d;9dYS&6$k`Irnvtg4HEe8x6Y+LdooGzMG6K-*nRTu3}K zpn$U%z-Ax_XDx)aP!P^q2y3AroV5_vLP5BH-9nq(4a{;k(DS>>Yz^QSt@B*}iQD)} zxRoeHtWPb`sSsWIATjnrca6Q!qn7xW7Fi^oC5~PpSMUfP!gbU|F5oTH zjfW6p6kKE#T?HI?(%LP}I3vuYx=CZbWy4g}uc~gCZ;YZSN;Uu^;0(9}UV#c3R=^x6 zVimp*oC6!;@&Fu27heGPq~lt;^Y!9yfhjNq_RKEB_u64R1i%SAcygJ=37`sfrIpjQR+$@4RQ|&g|NF9)Srk z=+&Y2yaV6}SOmE)`UDDh`|GuFyXl!o7GB4R@GMPNf{$qtCEEFxNq8zFB;eOsOxPx$ z3J?IDfJ3AsI)RQfbz3LU5w=-boqz!71RNqA(Ft^#*AI@P)2)u=lEcbam6RjNa?*Gh=yvFzZ z%tt2S?jN@YMQ_-dZVxixHCFMCiW1O^O}wMw9l)d5m}zd5 zkUb(3zX}XQi2GcNNxWlUiMb{+@pxAsbs|`>e*5eyQLY>L_NqydJ!|6g+NUdM6F3I0 zfG-ON@DOz+?9H2vym*mYG&6K22p-6x{T$eua6vm4`Z=Pz;`y4a0n;?ZIvuf7#o{O@ c5((nx2V8hddKMW`_W%F@07*qoM6N<$g7Ll1v;Y7A literal 0 HcmV?d00001 diff --git a/TimetableDesigner/Resources/Images/EditWhite.png b/TimetableDesigner/Resources/Images/EditWhite.png new file mode 100644 index 0000000000000000000000000000000000000000..576f0236d9d81286eaf3c2a5b95adea92510d79b GIT binary patch literal 525 zcmV+o0`mQdP)SJ^QZXI^5~R4h9r#$$wcOZP|8fg_a^^LCNU-u2n7BDum)TJPr#Qw zx0^8ZXaFPTGxDy82>EAV2UxeqF7V>qiB;pN{3Ea&(XZkZ(agtq zzI-{Me_i$MW^0=+^EW^#;haU)caW`d`U!g-R|K$>djT=AX{9NG$;8etLUEZFSt}h| zgiLJURkfdcA+OqFi?BsX|0#n^m(jGV4beqNygKT?T&JF;{2}n81Y}=RN$9@-TscK( z`6F-FdFM{J6I-s+Fusnwb8gwmeC8t`nWbt9E~4GX*$RVjs@kh~iDYq8VQR z?&k;(?MwS5u$AcJm0HUy0dHDwp P00000NkvXXu0mjfDGcPc literal 0 HcmV?d00001 diff --git a/TimetableDesigner/Resources/Images/Error.png b/TimetableDesigner/Resources/Images/Error.png new file mode 100644 index 0000000000000000000000000000000000000000..fc00b23a14304e978e181026568f5e701da5fc57 GIT binary patch literal 1418 zcmV;51$Fv~P)cS~%simc0n~(?Lk>rK|Hz6+| z5D4v5ckEoX4i%`-Qrqs;LB~JAXl)gSoq=?Q4?M~C1|DR31}C#UgKe0Zc}a8F`n!Mh&73!MU-pG!IqxWPo>9o|5ybE4{FlF4^AmV=;a54u0Ma%$(w4q2U@#o3F1&aAc+Nle{ei<<6$40hIv)uJ@9`?G zeC^<+P8XyOC!}`%2Q&p=t=K?K#>ToA-U+FVN6OqgUm^V1l|zI_svE=e$Or5~|$&a=Qvd;qa-Ka$-8%jSeIY zU7wc(|JUG^I+jaH@>5OcYq% z9TQcA$8vMW*XkqW4n8UMoR4#!f}q&iR$!|EztY@+L{t0k>LcV1O66Qqaa&(u6IFs= zZfeK!g*H+B3%G*$a&K6{;zDflma+$4fZyJ(6dubL%>Ve?#7DQVq_vg` zk9dPgeG_N+z{9G*?{3)ZHw*qRa$^=tmn_WiiE}0-8jR}4sM6ao!n0gb5rZA!>(hlz zOw8cV<_=bP2xkowss^ZZVvWR6dDciw8ps<#stnJcNZUMODG%`SZ&>rb@It*VC7d&q*PBvTdART`h}G+Yno2}ngs*QhCGx)c4-wJOSs#EUniYjmFDr+$|sMg9C5_OfL z$stx7K6bh#S@DKtRo2vflIlKD&#hMsU$0-Mb3RhnbV5^WrL>My)R?VkEs>Jp&*&o* z4@$ma4?+~OsC1&l;tVRW=A^Gku=5h Y58L4`DI)AW$N&HU07*qoM6N<$f=98fs{jB1 literal 0 HcmV?d00001 diff --git a/TimetableDesigner/Resources/Images/GroupBlack.png b/TimetableDesigner/Resources/Images/GroupBlack.png new file mode 100644 index 0000000000000000000000000000000000000000..44bf9189b609811ac2e9e05309411512ab8eb446 GIT binary patch literal 910 zcmV;919AL`P)0d%)+qjp542<30b&5 z92RD0=gfc4%$zyrLJ1|5kOWmgH&D>g1Jsfu=f46IzVS`pO?z|)IPZ;H$qVGUEP@Zs}ry&F$r*VD(Fs_DVSp{XlO-B{Fl5uG+q#;mm4E3sPy~BAK zW7BB`4jDroD!ULtKt`51urUpRyT(wn%AQRU0krDKFaA@XshpQsA23c;1M^HyOa_6> zcU!x|3ZZo|oS!xXnvFveN`iwa3Csc>Xozex@^YXR=r?8gBXB~$NrVpLkR)?d6KBWb z7*$P(2xb6x9JY=oCGgxBkI%yi&8t2m>(fpkG5$mlI8$U*!%_eD==(bT^*#BeGz6lS zS&o3kR5ihi7$IZBY$z)N9u}IdU!D~i-Y@l;kU0em>*+940{(+YL&mGRC5^+b$t>E8 zye(q_^;tPz;ULgZL>{;Zc(|NJBj?9?V^msRGRlJ>0!>CHr@z!#V;q#RH9#jx9#n@A zm=BDq>~GrJz8QKp^(MI&qE~Dl$R_p1eDm7Tw(LqnV7Di7$(v4}jF3p_IV!@ydf z&R+9g4pE7))OQ1Y*rDssgGC{FGlAR2rsxJ;9j^3kUUQNV=rl%N#V`^l588ck?+TB8 zqqiXm0o#MfZCn&i8~uGDI>SciNQAv}Mt6S#|NqzEg0{wSutHnz55Ou1$q{Y*${ypRI!QAt59gOX~4IN&tgRzjY;j@?L z1U`AW4#pDJhITL40aQrBX5t+%-^-&))L~*?e(ibV{6qn$0lI-fxvur{%=sYHCN+E% z7zPe0*;HT;@BkQtv%B6WA34tnQs83|o zgyVZIy@3X0s8wVw4g#c{18b5HxSAJ}3lMn!=0A13kHYs^Aun%~o%JM6q**p`MK&$epA;KX} zV@`ys)+Mq@z_k!t2jddBr;JpJ>`g=hZ5EqmmwlWO*?Nbq=Wz*q6$2yE&=!U)vR68@ z#=3}A?V^LgXj}q~LgLRW1U82(GFNokl)XH(TgW~=`@v452(!Ud2q;;pYW?O~n&Nz^-wzq!Ffe4k4pSu%@F7wY*QNy^!g#s8!5N$tZpV`K5km{vdRN#Qcu-0PeP6@+maC2t_%&^{)Lor{^@qu zoDSSk`a{5~44s|oerH_g=gG9q$NmuA%siNF(VGlhRW^<8xY1$8$<;KqX>kZ#Rz@DW zj6}%;89fpuJb=eaZ*?33`V3MXafa$+mqo{fi;n{idnc9d?l=S*rPVh@)L$$|?w4kU xWYIYSe3PUva@Z>eo|yy(7G+JKfCBy_{011sd;LZmNI3ui002ovPDHLkV1nqnn#uqG literal 0 HcmV?d00001 diff --git a/TimetableDesigner/Resources/Images/Info.png b/TimetableDesigner/Resources/Images/Info.png new file mode 100644 index 0000000000000000000000000000000000000000..75ffe11e0e80ad0abda0b0c6be6bf4fbec510a10 GIT binary patch literal 1367 zcmV-d1*rOoP)e~1+`))7_;wTGnkk_g98p=tV&f=wH13HFy2=%nPJAMqM#@$Uo}43 zQ3uZ(tRDDFa~=JDZ{GLj`yPf}>eJEtKhxS4$U2OYs6} zEuJS=i*>~PA|26Ms3ShkyGN#D|A$oRyn^y&`kKN(|5mYdd|JLlLHQyDW%rMvRDTR5 z3nY}xlTdt*fFd0Mg>wWH-hB)OKR*Wf?9pEZcaFZ4-z8!r4ql{%*DH zu}ndj@F1T#g8Vy2kT>n4a<#r28Cq9#MBo*MCvPiO=sm>>4Lra@uGR;+w+}(4Ik3qz zzIV?RzS2m4qgbI|@C~0c#RwdNY|{IWRPFr{1Bl|693ZOF=x?bs`r-WG4?sF`0NH9U zNX9+ts=n3Nc;l}!`c;vG_kv{13t1y>4OR`W@B#l_#NkIhkU8RkjA2&;61>7ly~B0S z%e8LEQ=1`g+zfdd7YBGZWDL1Z(}q0nbAr#+!=*|ijdOu7(%3-#hyd#3Mc^6VAs~O; z5+3|5H>3}_A#Kp*lMLH0hb>UK;=jRZ<)=MR^Tz=M=cfl=kf~ka!b94C3sUd82TfugzW`TQv-OgpV&7aHgGEKw+T2=321%S}$B%c7zCg zqPK%|+!Z#wxGx|8qHagcnFT7A=`EDgCP!_9&zBr<7bgSHrwshCg*$wY15&yi&NCgW zWripDyd0Gp56DJsARV!ROzlR3ztYVJ*dejg_7?l<8>lZ(;d92UkUg*qlKx$g^zA@a z?+!?Y%t-LbopwlWw|>w51s0EoQGZeld~hUY#XEVJq%0$tlk z@Ja19NKje#*ah^Y6SX~%^gav`$mrZcf=_H?1T0Tk1xocKh1%O>^@IwfcWfcSC#tNF zptPJ~T6pz81&ZehKWbx^*}WYikk+w@2H$Fh%SsDiY;AVQJn2R4DqkwlzKIMU-(o&t z-9p7WVgt1$W(qbqscoBx@D_+`Hal4bispzZ)P9~R*x-m&8%Xf+%@&AlG%v9V6wDFd zq7EZW!G;L5ZXm(OHJKTKM)of-OI}5tq?m#Yj!3zV3J-z?^LwlU%rPaO^?A6Df$l)T z2Ior4Iug9F0rz9o_BYrCFbtE=_!_ywGe;#;u)#^Wxy~6rrojZldVCN&Yh+q)R_?U# zITv{5sALK@M4)+%Gki=Pek!cpFT=t~&TZcc&GQ*0Lp#n)w>m1?nXP4@6S=a&ri3#6loMzaLy}+JlwhgoLNsVieq}&Z3zRm=K z8vHtjl5F+CB95o!1?SP;GYX>@E>@&k!Fji-ys$E zIcLSaj#GZ%@x_?h{Q@k~N$Ihbi+b#1Dc$y`;ltx6G4-aY=sKMJuIJoEI(K3cyX+q% zx7#|C+H8L&s%-wyVZ`!xY@=y4w!zdVtlR(a{PsCd5j7tQg(|B^pmd}%f_%OAA{W8< Z&A)|h%;wC=< literal 0 HcmV?d00001 diff --git a/TimetableDesigner/Resources/Images/Remove.png b/TimetableDesigner/Resources/Images/RemoveBlack.png similarity index 100% rename from TimetableDesigner/Resources/Images/Remove.png rename to TimetableDesigner/Resources/Images/RemoveBlack.png diff --git a/TimetableDesigner/Resources/Images/RemoveWhite.png b/TimetableDesigner/Resources/Images/RemoveWhite.png new file mode 100644 index 0000000000000000000000000000000000000000..04197453bca996c3268f273f9f918d9619beebe9 GIT binary patch literal 422 zcmV;X0a^ZuP)=XHGsJ?*ssz1N`Lzc zLzh6#=E;GqsrkK?iEf&Cn+;_Db^~?jiXjrj?BM}^0ltA9%10KMNZ$M!gQfsURJb)G7fWk7Q)>sXA04uw)fn1K{O|+B( zWb%7b)ykr005A( zwz6>Kf5TTFQDOe)0nlPR03i4q1@FpowX;PLXhAyOzBC`APG}IFj{^W`lTf-hA%Mt& z_z?XlR19>wp&1IH_+p?34D8@`baNt^Vs(^3bUJE}CmanR82Um@jK$HRD84`tk>w2u z4GN?(QK1;Of+(}|&!f1$4;f1}gMG!~6X zrqO>K;4fSJfdA&mPv1YggnHBeiEL;0zv)3ie>nz|g+0Pg#P_8BC8j?%VB*8*M3^Iy zNef{Rh}a`UDobT86RUYaSqFLh5p9W7KNcAS)!+SJzyXTyf7@4u0z+0S;Y_9Qx7PdV z;`$TAKzHfEbbmqz{}EbEH3~-wB?h`$P=bh5CSUbO6hFa({{ouR0%;5&^21fBe;shM@T#c6KN$3X?@+ zgsqvrwYd{9_}l8+GLW*?-w?=Jl~LY=)vRNnbOy~ggh2FN+ng`=4Q0|uEVeg;Xy(T+ zFa~NyB2oDD34`EPI~Ss>qYKwT{?&s`Ci2z({cN!Bk-=7P+#jK#VSfuAy+-)c73KSV zv+=J!e}=$*3=#g~#~30~`LlmcTUyxw&AS$`m=6)0LV_g zjnYIsH@xqb{^0ijsnnthQ|I$DMfv`lUs%7G_?+ZFTS3oB6UD|ph*QdT$1Oa6+$t1- zFg(l!pSi{v70DoZA8Sl%C6=kyoc*ma_dT9rr}4Zk+?t?|F;T0sX_QeikqNCr+*L3} zOTwYmB5KqxL~LC60prLF=MHCImyZ0ZJ-LFLx8_$^e=fR{`DXuS>#L-dl{x2Rsd|Zi z#U|WG9|bT5*C22ssCB^@nKUfsdf|npN2ezy`h`PZt&Q9KwkOCJFY629=D7?BRNmth zxlS9GU>hyrX}{i0u>*&SJT*U+)jgO{5k&Gzsa`99qpuo+7UesY$ue8fQ@i-D3o*LY zK_&noA+!1b0ol0n^6n-d5!yg5mPu z1noYH#|f!h63eYm+si0jyy+yn-56AC@XUv$U*$HbqMe)yO20+sZF4P!BDkUg!$$Kx zP4l_cgM;AtTf;ZTdvYmGO&@<;RxmuO_mjL1sY6l6+3$P;nSg4}h@ND|c;)!`VhT|@qg zf1R=!4dF_Jr%TSX^lg?+dQ-J zWKn|UvqRUr{d13a$qJqVo=tm)-*weQx~yxc(E?X0Ss^%6{bFI+LGFV@SWlLx2;j`k zmAmeQZpX1~$FVKFUiq>`=!O(;pnUg}&@$3)uM-#n7xmtf<49305IM^AkQlS}Tt(Fg zsXeh>Q@r22pvbEzF~M22PNAhM;)7%k$2Rh=aBrGU%t>7mX=VQ5Y)1sTyKN=9<*|ok zx>Dk>O7vS%97n#Uaq{MT&K-@%mLu*U?5}6yKdH1d7B?t7LwE$5)P+|@T~$eyh1Myy z_yV^8YxZefElXL>RFfZ2Y*V6bmCtf?*SnisI>{$lM$}wpmw{hJBYJy}j2VK$_H%@z zgdo6+s+N{5Lxt~O(i-=6>CTlcCu&F+neK0w-;uyY4jR~yu7PRQ>hm{u6@Ix8$CBvb zJR@C3QDcJL6KpwFiSZd{&w(+|-#r}-?txdBU5VXS=#_AKhy z)1z=1h56zB)04Xqr^i}$uR9qXbEl&7^5On_A%w}g_OlPg9aau*t6RtG2e4nyO51Sa z;3U6C4NdF6Kur(HE40ZH8sOJY;Xbe>j>+A)U5eV#=jSnaymj|I-3qh*BJlcsrko%p zrvEb(X)qVqm2J=U=2(7N+)$*W^yC3kN}(<5T*oLBxanN3Ugoq$H8O5ge&*My>Qc`* zE6thdXU}o~ul@?ZwZ9ub?O z-2mzuCPGq$I`fXFfa(v*U)ukQcI+&X9~wV7)O?xpNcX@Uamn6zf59UR@KKe@-}hgu z%qx*C*%C90*f@k=9OS5oG-Q-H!=&@J@lZ!pWmdMoDhw+I+(D^F?(xy)j1})QJRd#2 zR5tTV)TDiyhvCT6c#H6zx7#jYYv!d=<_z#X+dr5~GezBPCe1G{cUOb0g6o)y8$T$8aVe7aJ1-P?o@heVe|F!((4naC%%6Ecx7&^P^`dE zC`~?eL+01vaf9ov*sMT1Sx#|g?dcfU?U6SN{$Np%;AB^TZn6K?brv`8-FmLMadhCVNR@@=Na$90@v^lwcdhAxdUB~w6LL5kZ3y+s%M_wpPd5Cn2fYbbXE zxyniZCLAr2K20RuI@7Yy??F_h?mHGSUD{(^+VshS7u6k;VAvda==ba%8^PHona+A! zgXN9sV#Mb=(sSZc>~o5RjHJVJc__`PXhE;6yHH2V+b%D6lu180!<~EGIaqzrI!keH zzC&*4iyrgyPI<9?+W3(-Qh5s{wIV}C^IROy3OFO~I0U37j()7YjcCD#o43T*pR;{F zWvyp0i@zJA;i1(XbUQZ5Anvn7ne@5Gzd0U=u9v?NrX*N3#Z}fC{fb7i-|y2NMbCVV z1kh|#=lnkGAL$nu0! zjK<0pUkXemy@`BTZwZrCDT4HgGN2t>)Xhu$&!E>`J;fUbT>8+kYvIepe0lhzS(-xd VK(1A{^4kAuYplIR#U8Kd{{Y^RtsnpZ diff --git a/TimetableDesigner/Resources/Images/TeacherBlack.png b/TimetableDesigner/Resources/Images/TeacherBlack.png new file mode 100644 index 0000000000000000000000000000000000000000..4bc4b784506ce9fc7bcbf47a05ce378fdf28e4d1 GIT binary patch literal 446 zcmV;v0YUzWP)d{i@ z%{S4T zfn+6@q)eC2=S7lX)>;C(YS{EfZ@{6>*#qK?IdMroFXBxKK%X`P?fI$R`Q_o4L?so7 z+NwYZ$j%Bl0PcX7)FO*F?-=LvFKIp42zO?Uq>Vr)nO$zXV zm;kH-wGe9|tcAjFA@LORb^cC-C_wZxcdILKRYicycyNdbXc@5zw2W8*8n;kAkYHF+Cj-LvUQ096F2SH{Lu7J%5$6X(=z#El6^{SuAUJFlP7&*4)&!yE^) zbw3V-0COtx|9szy+`itOGs6XBUBO;2d}XjxBpQ!(UgxA;q`_PJta@4Hy_i z|M?fq3x5*=u4sl&;J~tnGyHW06ye5EQv=uoR!eQ1p=|*@odcDJOW+we1GZESN^RJM zfXD-g00-hg0jN4K*W|^45+DvZFik0#+X=!HpqTx42L`pmV_OdnL_o(N4s;CSK*t~s vbPT_@1xbsQ5$x zW#@d&y`>#2>+S7bNAJx2Cf}F!@ncY;`|8103X|?7ehNxtm6Gg|8~9td@H?hR z2k}dkWFNVOuS!VvA@M^{3TKq$x)J#DEieti^R0!SBuq*E!+h{xCRSr~IFCZ1fP6lW z;T>80dZK-STcAI}AiQ+~lD)Lhsh=6!fT5wGs=&a&0L~s?v*1lYvKLzeZ?dgQ@aqeW z%uk=Jwc!T`2W^2|E{B=%Rkhy2jh6i2Q<9Zx&HK#6>MFc-`j)`u$@`q%!zqBQxNGo` z;isG)>*dMzJK+2K`|)JA5{q#kHw6=dDy^f59N@ zms~&zcD&x2y!v_D9q`#~2D73zytY0Q_9z`&Qh1o*oD$Z~cYu)$vw~OQGnotq2U3pU zx*||y8U`;c4ZK0PlKgBv_)7D+v<;t5r!g{=s0**F67(FVNx10(O7K_R!du;0NTRQ= z52;iNyLWDP1+V8Mn)@#(itHh4UpFG~_>MHM}mrg@0hMuI9jltWc7k zYy{rwo{>k8OeT>?B(Q&X)HS@GllZDB-whD-K%?+h_w9-y9*^67dQWFV@cLF!q|Yo4 zewwUPg8k`5;N&~&@&0pL@ZO>O@%6iFJPD{GyMiww8$Ams!6v-GH!|!;`m?syY0Vu zf!BgUiO>uriMSR}lD@H+u^BtM5wTbd(P-3ecXv0&j&AS-uPYKo`ntQ*FVUD1?BB)A zeCe$Y8`^4DR~I@vJ0aif@C>gl0fXMe6y4?w|1!BpN%l=M9_$B)HX$8<5UK53@%FQu ztc~IgzBpH9(9;%UZ2=iN*osY~Yn=M+@Rh;{9dw%g3x0;z_AP|YI|*F$GrTSfTyPj6 sHsVU6h42t7haDeskRU;V1R)LNKaKyUXPWM(cmMzZ07*qoM6N<$g5(hK{Qv*} literal 0 HcmV?d00001 diff --git a/TimetableDesigner/Services/FileDialog/FileDialogService.cs b/TimetableDesigner/Services/FileDialog/FileDialogService.cs new file mode 100644 index 0000000..b2c32f4 --- /dev/null +++ b/TimetableDesigner/Services/FileDialog/FileDialogService.cs @@ -0,0 +1,78 @@ +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TimetableDesigner.Properties; + +namespace TimetableDesigner.Services.FileDialog +{ + public class FileDialogService : IFileDialogService + { + #region PUBLIC METHODS + + public string? OpenFile() => OpenFile(new Dictionary>(), true); + public string? OpenFile(IDictionary> types, bool allowAllFiles = false) + { + OpenFileDialog fileDialog = new OpenFileDialog(); + return BuildAndShowDialog(fileDialog, types, allowAllFiles); + } + + public string? SaveFile() => SaveFile(new Dictionary>(), true); + public string? SaveFile(IDictionary> types, bool allowAllFiles = false) + { + SaveFileDialog fileDialog = new SaveFileDialog(); + return BuildAndShowDialog(fileDialog, types, allowAllFiles); + } + + #endregion + + + + #region PRIVATE METHODS + + private static string? BuildAndShowDialog(Microsoft.Win32.FileDialog fileDialog, IDictionary> types, bool allowAllFiles) + { + fileDialog.Filter = BuildFilterString(types, allowAllFiles); + + if (fileDialog.ShowDialog() == true) + { + return fileDialog.FileName; + } + else + { + return null; + } + } + + private static string BuildFilterString(IDictionary> types, bool allFiles) + { + List typeStrings = new List(); + foreach (KeyValuePair> type in types) + { + StringBuilder sb = new StringBuilder(); + sb.Append($"{type.Key}|"); + + List extensionStrings = new List(); + foreach (string extension in type.Value) + { + extensionStrings.Add($"*.{extension}"); + } + + sb.Append(string.Join(';', extensionStrings)); + + typeStrings.Add(sb.ToString()); + } + + if (allFiles) + { + typeStrings.Add($"{Resources.Global_AllFiles}|*.*"); + } + + return string.Join(" | ", typeStrings); + } + + #endregion + } +} diff --git a/TimetableDesigner/Services/FileDialog/IFileDialogService.cs b/TimetableDesigner/Services/FileDialog/IFileDialogService.cs new file mode 100644 index 0000000..a3b299a --- /dev/null +++ b/TimetableDesigner/Services/FileDialog/IFileDialogService.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TimetableDesigner.Services.FileDialog +{ + public interface IFileDialogService : IService + { + string? OpenFile(); + string? OpenFile(IDictionary> types, bool allowAllFiles = false); + + string? SaveFile(); + string? SaveFile(IDictionary> types, bool allowAllFiles = false); + } +} diff --git a/TimetableDesigner/Services/MessageBox/IMessageBoxService.cs b/TimetableDesigner/Services/MessageBox/IMessageBoxService.cs index 4a9ef90..e9b0754 100644 --- a/TimetableDesigner/Services/MessageBox/IMessageBoxService.cs +++ b/TimetableDesigner/Services/MessageBox/IMessageBoxService.cs @@ -16,6 +16,12 @@ namespace TimetableDesigner.Services.MessageBox MessageBoxQuestionResult ShowQuestion(string message, bool hideCancelButton = false); MessageBoxQuestionResult ShowQuestion(string message, string title, bool hideCancelButton = false); + void ShowWarning(string message); + void ShowWarning(string message, string title); + + void ShowInformation(string message); + void ShowInformation(string message, string title); + #endregion } } diff --git a/TimetableDesigner/Services/MessageBox/MessageBoxService.cs b/TimetableDesigner/Services/MessageBox/MessageBoxService.cs index 440f730..0c1faf7 100644 --- a/TimetableDesigner/Services/MessageBox/MessageBoxService.cs +++ b/TimetableDesigner/Services/MessageBox/MessageBoxService.cs @@ -34,6 +34,12 @@ namespace TimetableDesigner.Services.MessageBox } } + public void ShowWarning(string message) => ShowWarning(message, Resources.MessageBox_Warning); + public void ShowWarning(string message, string title) => System.Windows.Forms.MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Warning); + + public void ShowInformation(string message) => ShowInformation(message, Resources.MessageBox_Information); + public void ShowInformation(string message, string title) => System.Windows.Forms.MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Information); + #endregion } } diff --git a/TimetableDesigner/Services/Project/IProjectService.cs b/TimetableDesigner/Services/Project/IProjectService.cs index d445589..cd1d94a 100644 --- a/TimetableDesigner/Services/Project/IProjectService.cs +++ b/TimetableDesigner/Services/Project/IProjectService.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; +using TimetableDesigner.Customs; using TimetableDesigner.ViewModels.Models; namespace TimetableDesigner.Services.Project @@ -12,7 +15,12 @@ namespace TimetableDesigner.Services.Project #region PROPERTIES Core.Project? Project { get; } - ProjectViewModel? ProjectViewModel { get; } + ProjectVM? ProjectViewModel { get; } + + ObservableCollection Errors { get; } + + string? SavePath { get; } + ObservableDictionary RecentProjects { get; } #endregion @@ -21,11 +29,14 @@ namespace TimetableDesigner.Services.Project #region METHODS void New(); - void Load(string path); - void Save(string path); + void RefreshErrors(); + + void LoadRecentProjectsList(); + void SaveRecentProjectsList(); + #endregion } } diff --git a/TimetableDesigner/Services/Project/ProjectError.cs b/TimetableDesigner/Services/Project/ProjectError.cs new file mode 100644 index 0000000..45992b3 --- /dev/null +++ b/TimetableDesigner/Services/Project/ProjectError.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TimetableDesigner.Customs; +using TimetableDesigner.ViewModels.Models; +using TimetableDesigner.ViewModels.Models.Base; + +namespace TimetableDesigner.Services.Project +{ + public class ProjectError : ObservableObject + { + #region FIELDS + + private ProjectErrorType _type; + private IUnitVM _unit; + private ClassVM _class; + private string _description; + + #endregion + + + + #region PROPERTIES + + public ProjectErrorType Type + { + get => _type; + set + { + if (_type != value) + { + _type = value; + NotifyPropertyChanged(nameof(Type)); + } + } + } + public IUnitVM Unit + { + get => _unit; + set + { + if (_unit != value) + { + _unit = value; + NotifyPropertyChanged(nameof(Unit)); + } + } + } + public ClassVM Class + { + get => _class; + set + { + if (value != _class) + { + _class = value; + NotifyPropertyChanged(nameof(Class)); + } + } + } + public string Description + { + get => _description; + set + { + if (value != _description) + { + _description = value; + NotifyPropertyChanged(nameof(Description)); + } + } + } + + #endregion + + + + #region CONSTRUCTORS + + public ProjectError(ProjectErrorType type, IUnitVM unit, ClassVM @class, string description) + { + Type = type; + Unit = unit; + Class = @class; + Description = description; + } + + #endregion + } +} diff --git a/TimetableDesigner/Services/Project/ProjectErrorType.cs b/TimetableDesigner/Services/Project/ProjectErrorType.cs new file mode 100644 index 0000000..7f112c3 --- /dev/null +++ b/TimetableDesigner/Services/Project/ProjectErrorType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TimetableDesigner.Services.Project +{ + public enum ProjectErrorType + { + Error, + Warning, + Information + } +} diff --git a/TimetableDesigner/Services/Project/ProjectService.cs b/TimetableDesigner/Services/Project/ProjectService.cs index fad1fa0..1f72e48 100644 --- a/TimetableDesigner/Services/Project/ProjectService.cs +++ b/TimetableDesigner/Services/Project/ProjectService.cs @@ -1,12 +1,20 @@ -using System; +using Microsoft.VisualBasic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.Diagnostics; +using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Windows.Input; using TimetableDesigner.Core; using TimetableDesigner.Customs; using TimetableDesigner.Properties; using TimetableDesigner.ViewModels.Models; +using TimetableDesigner.ViewModels.Models.Base; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace TimetableDesigner.Services.Project { @@ -15,7 +23,19 @@ namespace TimetableDesigner.Services.Project #region FIELDS private Core.Project? _project; - private ProjectViewModel? _projectViewModel; + private ProjectVM? _projectViewModel; + + private string? _savePath; + private ObservableDictionary _recentProjects; + + private ObservableCollection _errors; + + private static readonly JsonSerializerSettings _serializationSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + PreserveReferencesHandling = PreserveReferencesHandling.All, + NullValueHandling = NullValueHandling.Include, + }; #endregion @@ -35,7 +55,7 @@ namespace TimetableDesigner.Services.Project } } } - public ProjectViewModel? ProjectViewModel + public ProjectVM? ProjectViewModel { get => _projectViewModel; private set @@ -48,6 +68,44 @@ namespace TimetableDesigner.Services.Project } } + public string? SavePath + { + get => _savePath; + private set + { + if (_savePath != value) + { + _savePath = value; + NotifyPropertyChanged(nameof(SavePath)); + } + } + } + public ObservableDictionary RecentProjects + { + get => _recentProjects; + private set + { + if (_recentProjects != value) + { + _recentProjects = value; + NotifyPropertyChanged(nameof(RecentProjects)); + } + } + } + + public ObservableCollection Errors + { + get => _errors; + set + { + if (_errors != value) + { + _errors = value; + NotifyPropertyChanged(nameof(Errors)); + } + } + } + #endregion @@ -56,8 +114,13 @@ namespace TimetableDesigner.Services.Project public ProjectService() { - _project = null; - _projectViewModel = null; + Project = null; + ProjectViewModel = null; + + SavePath = null; + RecentProjects = new ObservableDictionary(); + + Errors = new ObservableCollection(); } #endregion @@ -72,17 +135,234 @@ namespace TimetableDesigner.Services.Project { Name = Resources.Global_DefaultProjectName, }; - ProjectViewModel = new ProjectViewModel(Project); + ProjectViewModel = new ProjectVM(Project); + SavePath = null; + RefreshErrors(); } - public void Load(string path) + public async void Load(string path) { + using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read)) + using (StreamReader reader = new StreamReader(stream)) + { + string content = reader.ReadToEnd(); + Project = JsonConvert.DeserializeObject(content, _serializationSettings); + ProjectViewModel = new ProjectVM(Project); + } + SavePath = path; + RefreshErrors(); } public void Save(string path) { + using (FileStream stream = File.Open(path, FileMode.Create, FileAccess.Write)) + using (StreamWriter writer = new StreamWriter(stream)) + { + string content = JsonConvert.SerializeObject(Project, _serializationSettings); + writer.Write(content); + } + SavePath = path; + + if (!RecentProjects.ContainsKey(Project.Guid)) + { + if (RecentProjects.Count == Settings.Default.RecentProjectsCount) + { + IEnumerable> kvp = RecentProjects.Cast>(); + RecentProjects.Remove(kvp.Where(x => x.Value.SaveDate <= kvp.Min(y => y.Value.SaveDate)).FirstOrDefault()); + } + RecentProjects.Add(Project.Guid, new(Project.Name, DateTime.Now, SavePath)); + } + else + { + RecentProjects[Project.Guid] = new(Project.Name, DateTime.Now, SavePath); + } + SaveRecentProjectsList(); + } + + + public void RefreshErrors() + { + Errors.Clear(); + + foreach (ClassVM class1 in ProjectViewModel.Classes) + { + foreach (ClassVM class2 in ProjectViewModel.Classes) + { + if (class1.Equals(class2)) + { + continue; + } + + if (class1.Day is not null + && + class2.Day is not null + && + class1.Day.Equals(class2.Day) + && + class1.Slot is not null + && + class2.Slot is not null + && + class1.Slot.Equals(class2.Slot)) + { + // same classroom at the same time + bool classroomCondition = class1.Classroom is not null + && + class2.Classroom is not null + && + class1.Classroom.Equals(class2.Classroom); + if (classroomCondition) + { + ProjectError newEntry = new ProjectError(ProjectErrorType.Error, class2.Classroom, class2, Resources.Errors_ClassroomBusy); + Errors.Add(newEntry); + } + + // same teacher at the same time + bool teacherCondition = class1.Teacher is not null + && + class2.Teacher is not null + && + class1.Teacher.Equals(class2.Teacher); + if (teacherCondition) + { + ProjectError newEntry = new ProjectError(ProjectErrorType.Error, class2.Teacher, class2, Resources.Errors_TeacherBusy); + Errors.Add(newEntry); + } + + // same group at the same time + if (class1.Group is not null && class2.Group is not null) + { + if (class1.Group.Equals(class2.Group)) + { + ProjectError newEntry = new ProjectError(ProjectErrorType.Error, class2.Group, class2, Resources.Errors_GroupBusy); + Errors.Add(newEntry); + } + else if (class1.Group is GroupVM group1 && class2.Group is SubgroupVM subgroup2 && group1.AssignedSubgroups.Contains(subgroup2)) + { + ProjectError newEntry = new ProjectError(ProjectErrorType.Error, class2.Group, class2, Resources.Errors_GroupPartBusy); + Errors.Add(newEntry); + } + else if (class1.Group is SubgroupVM subgroup1 && class2.Group is GroupVM group2 && group2.AssignedSubgroups.Contains(subgroup1)) + { + ProjectError newEntry = new ProjectError(ProjectErrorType.Error, class2.Group, class2, Resources.Errors_GroupAllBusy); + Errors.Add(newEntry); + } + } + } + } + + bool teacherHoursCondition = class1.Teacher is not null + && + class1.Day is not null + && + class1.Slot is not null + && + ( + !class1.Teacher.AvailabilityHours.ContainsKey(class1.Day) + || + ( + class1.Teacher.AvailabilityHours.ContainsKey(class1.Day) + && + !class1.Teacher.AvailabilityHours[class1.Day].Any(x => x.CheckCollision(class1.Slot) == TimetableSpanCollision.CheckedSlotIn) + ) + ); + if (teacherHoursCondition) + { + ProjectError newEntry = new ProjectError(ProjectErrorType.Warning, class1.Teacher, class1, Resources.Errors_TeacherNotAvailable); + Errors.Add(newEntry); + } + + bool teacherAssigned = class1.Teacher is not null; + bool classroomAssigned = class1.Classroom is not null; + bool groupAssigned = class1.Group is not null; + + List assigned = new List(); + if (groupAssigned) + { + assigned.Add(class1.Group); + } + if (teacherAssigned) + { + assigned.Add(class1.Teacher); + } + if (classroomAssigned) + { + assigned.Add(class1.Classroom); + } + IUnitVM unit = assigned.FirstOrDefault(); + + if (!teacherAssigned) + { + ProjectError newEntry = new ProjectError(ProjectErrorType.Warning, unit, class1, Resources.Errors_TeacherNotAssigned); + Errors.Add(newEntry); + } + if (!classroomAssigned) + { + ProjectError newEntry = new ProjectError(ProjectErrorType.Warning, unit, class1, Resources.Errors_ClassroomNotAssigned); + Errors.Add(newEntry); + } + if (!groupAssigned) + { + ProjectError newEntry = new ProjectError(ProjectErrorType.Warning, unit, class1, Resources.Errors_GroupNotAssigned); + Errors.Add(newEntry); + } + + if (class1.Slot is null || class1.Day is null) + { + ProjectError newEntry = new ProjectError(ProjectErrorType.Information, unit, class1, Resources.Errors_SlotNotAssigned); + Errors.Add(newEntry); + } + } + } + + + public void LoadRecentProjectsList() + { + RecentProjects.Clear(); + + if (!File.Exists(Globals.Path.RecentProjectsFile)) + { + return; + } + + string[] projects; + using (FileStream stream = File.Open(Globals.Path.RecentProjectsFile, FileMode.Open, FileAccess.Read)) + using (StreamReader reader = new StreamReader(stream)) + { + projects = reader.ReadToEnd().Split('\n'); + } + foreach (string project in projects) + { + string[] projectData = project.Split(" : "); + if (projectData.Length < 4) + { + continue; + } + + string name = projectData[1]; + string path = projectData[3]; + if (Guid.TryParse(projectData[0], out Guid guid) && DateTime.TryParse(projectData[2], out DateTime saveDate) && !string.IsNullOrWhiteSpace(path) && File.Exists(path) && !string.IsNullOrWhiteSpace(name)) + { + RecentProjects.Add(guid, new(name, saveDate, path)); + } + } + } + + public void SaveRecentProjectsList() + { + List lines = new List(); + foreach (ObservableKeyValuePair project in RecentProjects) + { + lines.Add($"{project.Key} : {project.Value.Name} : {project.Value.SaveDate} : {project.Value.Path}"); + } + + using (FileStream stream = File.Open(Globals.Path.RecentProjectsFile, FileMode.Create, FileAccess.Write)) + using (StreamWriter reader = new StreamWriter(stream)) + { + reader.Write(string.Join('\n', lines)); + } } #endregion diff --git a/TimetableDesigner/Services/Project/RecentProjectEntry.cs b/TimetableDesigner/Services/Project/RecentProjectEntry.cs new file mode 100644 index 0000000..7acc315 --- /dev/null +++ b/TimetableDesigner/Services/Project/RecentProjectEntry.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TimetableDesigner.Customs; + +namespace TimetableDesigner.Services.Project +{ + public class RecentProjectEntry : ObservableObject + { + #region FIELDS + + private string _name; + private DateTime _saveDate; + private string _path; + + #endregion + + + + #region PROPERTIES + + public string Name + { + get => _name; + set + { + if (_name != value) + { + _name = value; + NotifyPropertyChanged(nameof(Name)); + } + } + } + public DateTime SaveDate + { + get => _saveDate; + set + { + if (_saveDate != value) + { + _saveDate = value; + NotifyPropertyChanged(nameof(SaveDate)); + } + } + } + public string Path + { + get => _path; + set + { + if (_path != value) + { + _path = value; + NotifyPropertyChanged(nameof(Path)); + } + } + } + + #endregion + + + + #region CONSTRUCTORS + + public RecentProjectEntry(string name, DateTime saveDate, string path) + { + Name = name; + SaveDate = saveDate; + Path = path; + } + + #endregion + } +} diff --git a/TimetableDesigner/Services/Scheduler/ISchedulerService.cs b/TimetableDesigner/Services/Scheduler/ISchedulerService.cs new file mode 100644 index 0000000..502c75f --- /dev/null +++ b/TimetableDesigner/Services/Scheduler/ISchedulerService.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TimetableDesigner.Core; +using TimetableDesigner.ViewModels.Models; + +namespace TimetableDesigner.Services.Scheduler +{ + public interface ISchedulerService : IService + { + #region METHODS + + (TimetableDay? Day, TimetableSpan? Slot) Schedule(ClassVM @class); + + #endregion + } +} diff --git a/TimetableDesigner/Services/Scheduler/SchedulerService.cs b/TimetableDesigner/Services/Scheduler/SchedulerService.cs new file mode 100644 index 0000000..db0c6c8 --- /dev/null +++ b/TimetableDesigner/Services/Scheduler/SchedulerService.cs @@ -0,0 +1,201 @@ +using Microsoft.ML; +using Microsoft.ML.Data; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Data; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using TimetableDesigner.Core; +using TimetableDesigner.Services.Project; +using TimetableDesigner.ViewModels.Models; + +namespace TimetableDesigner.Services.Scheduler +{ + public class SchedulerService : ISchedulerService + { + #region FIELDS + + private IProjectService _projectService; + + #endregion + + + + #region CONSTRUCTORS + + public SchedulerService(IProjectService projectService) + { + _projectService = projectService; + } + + #endregion + + + + #region PUBLIC METHODS + + public (TimetableDay? Day, TimetableSpan? Slot) Schedule(ClassVM @class) + { + IEnumerable<(TimetableDay Day, TimetableSpan Slot)> freeSlots = GetFreeSlots(@class); + + List<(TimetableDay Day, TimetableSpan Slot)> allMidBreaks = new List<(TimetableDay Day, TimetableSpan Slot)>(); + List<(TimetableDay Day, TimetableSpan Slot)> secondTierBreaks = new List<(TimetableDay Day, TimetableSpan Slot)>(); + + IEnumerable days = freeSlots.Select(x => x.Day).Distinct(); + foreach (TimetableDay day in days) + { + IEnumerable unusedIndexes = freeSlots.Where(x => x.Day == day).Select(x => _projectService.ProjectViewModel.TimetableTemplate.Slots.IndexOf(x.Slot)); + Debug.WriteLine(day.Name); + foreach (int unusedIndex in unusedIndexes) + { + Debug.WriteLine(unusedIndex); + } + Debug.WriteLine(""); + + IEnumerable<(int Start, int End)> breaks = FindBreaks(unusedIndexes); + int last = _projectService.ProjectViewModel.TimetableTemplate.Slots.Count - 1; + + IEnumerable<(int Start, int End)> midBreaks = breaks.Where(x => x.Start != 0 && x.End != last); + if (midBreaks.Any()) + { + allMidBreaks.Add((day, _projectService.ProjectViewModel.TimetableTemplate.Slots.ElementAt(midBreaks.First().Start))); + } + else + { + (int start, int end) = breaks.First(); + int selIndex = start == 0 && end != last ? end : start; + secondTierBreaks.Add((day, _projectService.ProjectViewModel.TimetableTemplate.Slots.ElementAt(selIndex))); + } + } + + if (allMidBreaks.Any()) + { + return allMidBreaks.First(); + } + + if (secondTierBreaks.Any()) + { + Random rd = new Random(); + return secondTierBreaks.ElementAt(rd.Next(secondTierBreaks.Count)); + } + + return (null, null); + } + + #endregion + + + + #region PRIVATE METHODS + + private IEnumerable<(int Start, int End)> FindBreaks(IEnumerable unusedIndexes) + { + unusedIndexes = unusedIndexes.Order(); + + int prev = unusedIndexes.ElementAt(0); + int first = unusedIndexes.ElementAt(0); + List<(int Start, int End)> breaks = new List<(int Start, int End)>(); + foreach (int index in unusedIndexes.Skip(1)) + { + if (prev + 1 != index) + { + breaks.Add((first, prev)); + first = index; + } + prev = index; + } + breaks.Add((first, prev)); + return breaks; + } + + private Dictionary> CreateSlotsDictionary() + { + Dictionary> slotsDictionary = new Dictionary>(); + foreach (TimetableDay day in _projectService.ProjectViewModel.TimetableTemplate.Days) + { + slotsDictionary[day] = new List(); + foreach (TimetableSpan slot in _projectService.ProjectViewModel.TimetableTemplate.Slots) + { + slotsDictionary[day].Add(slot); + } + } + return slotsDictionary; + } + + private IEnumerable<(TimetableDay Day, TimetableSpan Slot)> GetFreeSlots(ClassVM @class) + { + Dictionary> classroomFreeSlotsDict = CreateSlotsDictionary(); + if (@class.Classroom is not null) + { + IEnumerable classroomClasses = _projectService.ProjectViewModel.Classes.Where(x => x.Classroom == @class.Classroom).Where(x => x.Day is not null && x.Slot is not null); + foreach (ClassVM classroomClass in classroomClasses) + { + classroomFreeSlotsDict[classroomClass.Day].Remove(classroomClass.Slot); + } + } + ICollection<(TimetableDay Day, TimetableSpan Slot)> classroomFreeSlots = new List<(TimetableDay Day, TimetableSpan Slot)>(); + foreach (KeyValuePair> pair in classroomFreeSlotsDict) + { + foreach (TimetableSpan slot in pair.Value) + { + classroomFreeSlots.Add((pair.Key, slot)); + } + } + + Dictionary> teacherFreeSlotsDict = CreateSlotsDictionary(); + if (@class.Teacher is not null) + { + IEnumerable teacherClasses = _projectService.ProjectViewModel.Classes.Where(x => x.Teacher == @class.Teacher).Where(x => x.Day is not null && x.Slot is not null); + foreach (ClassVM teacherClass in teacherClasses) + { + teacherFreeSlotsDict[teacherClass.Day].Remove(teacherClass.Slot); + } + } + ICollection<(TimetableDay Day, TimetableSpan Slot)> teacherFreeSlots = new List<(TimetableDay Day, TimetableSpan Slot)>(); + foreach (KeyValuePair> pair in teacherFreeSlotsDict) + { + foreach (TimetableSpan slot in pair.Value) + { + teacherFreeSlots.Add((pair.Key, slot)); + } + } + + Dictionary> groupFreeSlotsDict = CreateSlotsDictionary(); + if (@class.Group is not null) + { + IEnumerable groupClasses; + if (@class.Group is SubgroupVM subgroup) + { + IEnumerable groups = _projectService.ProjectViewModel.Groups.Where(x => x.AssignedSubgroups.Contains(subgroup)); + groupClasses = _projectService.ProjectViewModel.Classes.Where(x => (x.Group is GroupVM group && groups.Contains(group)) || x.Group == subgroup); + } + else + { + GroupVM group = @class.Group as GroupVM; + groupClasses = _projectService.ProjectViewModel.Classes.Where(x => x.Group == group || (x.Group is SubgroupVM subgroup && group.AssignedSubgroups.Contains(subgroup))); + } + groupClasses = groupClasses.Where(x => x.Day is not null && x.Slot is not null); + foreach (ClassVM groupClass in groupClasses) + { + groupFreeSlotsDict[groupClass.Day].Remove(groupClass.Slot); + } + } + ICollection<(TimetableDay Day, TimetableSpan Slot)> groupFreeSlots = new List<(TimetableDay Day, TimetableSpan Slot)>(); + foreach (KeyValuePair> pair in groupFreeSlotsDict) + { + foreach (TimetableSpan slot in pair.Value) + { + groupFreeSlots.Add((pair.Key, slot)); + } + } + + return classroomFreeSlots.Intersect(teacherFreeSlots).Intersect(groupFreeSlots); + } + + #endregion + } +} diff --git a/TimetableDesigner/Services/TabNavigation/TabItem.cs b/TimetableDesigner/Services/TabNavigation/TabItem.cs index a1ffebf..6aa93c7 100644 --- a/TimetableDesigner/Services/TabNavigation/TabItem.cs +++ b/TimetableDesigner/Services/TabNavigation/TabItem.cs @@ -21,7 +21,7 @@ namespace TimetableDesigner.Services.TabNavigation private ImageSource _image; private string _title; private bool _isClosable; - private BaseViewViewModel _viewModel; + private IViewVM _viewModel; #endregion @@ -65,7 +65,7 @@ namespace TimetableDesigner.Services.TabNavigation } } } - public BaseViewViewModel ViewModel + public IViewVM ViewModel { get => _viewModel; set diff --git a/TimetableDesigner/Services/TabNavigation/TabNavigationService.cs b/TimetableDesigner/Services/TabNavigation/TabNavigationService.cs index b29f838..08aa3b0 100644 --- a/TimetableDesigner/Services/TabNavigation/TabNavigationService.cs +++ b/TimetableDesigner/Services/TabNavigation/TabNavigationService.cs @@ -73,7 +73,7 @@ namespace TimetableDesigner.Services.TabNavigation public void Close(TabItem item) => Close(new List() { item }); public void Close(IEnumerable items) { - TabItem selected = SelectedTab; + TabItem? selected = SelectedTab; while (items.Contains(selected) && selected != null) { int nextIndex = Tabs.IndexOf(selected) + 1; @@ -102,7 +102,7 @@ namespace TimetableDesigner.Services.TabNavigation } } } - foreach (TabItem item in items) + foreach (TabItem item in items.ToList()) { Tabs.Remove(item); } diff --git a/TimetableDesigner/Settings.cs b/TimetableDesigner/Settings.cs new file mode 100644 index 0000000..b8c4698 --- /dev/null +++ b/TimetableDesigner/Settings.cs @@ -0,0 +1,28 @@ +namespace TimetableDesigner.Properties { + + + // This class allows you to handle specific events on the settings class: + // The SettingChanging event is raised before a setting's value is changed. + // The PropertyChanged event is raised after a setting's value is changed. + // The SettingsLoaded event is raised after the setting values are loaded. + // The SettingsSaving event is raised before the setting values are saved. + internal sealed partial class Settings { + + public Settings() { + // // To add event handlers for saving and changing settings, uncomment the lines below: + // + // this.SettingChanging += this.SettingChangingEventHandler; + // + // this.SettingsSaving += this.SettingsSavingEventHandler; + // + } + + private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { + // Add code to handle the SettingChangingEvent event here. + } + + private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { + // Add code to handle the SettingsSaving event here. + } + } +} diff --git a/TimetableDesigner/TimetableDesigner.csproj b/TimetableDesigner/TimetableDesigner.csproj index 3e0ee7c..8b189fe 100644 --- a/TimetableDesigner/TimetableDesigner.csproj +++ b/TimetableDesigner/TimetableDesigner.csproj @@ -16,29 +16,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always @@ -70,7 +127,10 @@ Always - + + Always + + Always @@ -88,11 +148,23 @@ Always + + Always + + + Always + + + Always + + + + @@ -117,6 +189,11 @@ True Resources.resx + + True + True + Settings.settings + @@ -130,4 +207,11 @@ + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + diff --git a/TimetableDesigner/ViewModels/BaseViewViewModel.cs b/TimetableDesigner/ViewModels/IModelVM.cs similarity index 64% rename from TimetableDesigner/ViewModels/BaseViewViewModel.cs rename to TimetableDesigner/ViewModels/IModelVM.cs index d86ea09..4dc32c5 100644 --- a/TimetableDesigner/ViewModels/BaseViewViewModel.cs +++ b/TimetableDesigner/ViewModels/IModelVM.cs @@ -3,11 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using TimetableDesigner.Customs; namespace TimetableDesigner.ViewModels { - public abstract class BaseViewViewModel : ObservableObject + public interface IModelVM { } } diff --git a/TimetableDesigner/ViewModels/IRemovableVM.cs b/TimetableDesigner/ViewModels/IRemovableVM.cs new file mode 100644 index 0000000..3d68afe --- /dev/null +++ b/TimetableDesigner/ViewModels/IRemovableVM.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TimetableDesigner.ViewModels.Models.Base +{ + public interface IRemovableVM : IUnitVM + { + } +} diff --git a/TimetableDesigner/ViewModels/IUnitEditorViewVM.cs b/TimetableDesigner/ViewModels/IUnitEditorViewVM.cs new file mode 100644 index 0000000..555636a --- /dev/null +++ b/TimetableDesigner/ViewModels/IUnitEditorViewVM.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TimetableDesigner.ViewModels.Models.Base; + +namespace TimetableDesigner.ViewModels +{ + internal interface IUnitEditorViewVM : IViewVM + { + #region PROPERTIES + + IUnitVM Unit { get; } + + #endregion + } +} diff --git a/TimetableDesigner/ViewModels/Models/IGroupViewModel.cs b/TimetableDesigner/ViewModels/IUnitVM.cs similarity index 65% rename from TimetableDesigner/ViewModels/Models/IGroupViewModel.cs rename to TimetableDesigner/ViewModels/IUnitVM.cs index 2b054d9..2df389b 100644 --- a/TimetableDesigner/ViewModels/Models/IGroupViewModel.cs +++ b/TimetableDesigner/ViewModels/IUnitVM.cs @@ -5,13 +5,13 @@ using System.Text; using System.Threading.Tasks; using TimetableDesigner.Core; -namespace TimetableDesigner.ViewModels.Models +namespace TimetableDesigner.ViewModels.Models.Base { - public interface IGroupViewModel + public interface IUnitVM : IModelVM { #region PROPERTIES - - IGroup Group { get; } + + IUnit Unit { get; } string Name { get; } #endregion diff --git a/TimetableDesigner/ViewModels/BaseModelViewModel.cs b/TimetableDesigner/ViewModels/IViewVM.cs similarity index 58% rename from TimetableDesigner/ViewModels/BaseModelViewModel.cs rename to TimetableDesigner/ViewModels/IViewVM.cs index 37898b9..ece4fe4 100644 --- a/TimetableDesigner/ViewModels/BaseModelViewModel.cs +++ b/TimetableDesigner/ViewModels/IViewVM.cs @@ -1,14 +1,12 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; -using TimetableDesigner.Customs; namespace TimetableDesigner.ViewModels { - public abstract class BaseModelViewModel : ObservableObject + public interface IViewVM { } } diff --git a/TimetableDesigner/ViewModels/Models/BaseGroupVM.cs b/TimetableDesigner/ViewModels/Models/BaseGroupVM.cs new file mode 100644 index 0000000..5dc5e68 --- /dev/null +++ b/TimetableDesigner/ViewModels/Models/BaseGroupVM.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TimetableDesigner.Core; +using TimetableDesigner.Customs; +using TimetableDesigner.ViewModels.Models.Base; + +namespace TimetableDesigner.ViewModels.Models +{ + public abstract class BaseGroupVM : ObservableObject, IModelVM, IUnitVM + { + #region FIELDS + + protected BaseGroup _baseGroup; + + #endregion + + + + #region PROPERTIES + + IUnit IUnitVM.Unit => _baseGroup; + public BaseGroup BaseGroup => _baseGroup; + + public string Name + { + get => _baseGroup.Name; + set + { + if (_baseGroup.Name != value) + { + _baseGroup.Name = value; + NotifyPropertyChanged(nameof(Name)); + } + } + } + public string ShortName + { + get => _baseGroup.ShortName; + set + { + if (_baseGroup.ShortName != value) + { + _baseGroup.ShortName = value; + NotifyPropertyChanged(nameof(ShortName)); + } + } + } + + #endregion + + + + #region CONSTRUCTORS + + public BaseGroupVM(BaseGroup baseGroup) + { + _baseGroup = baseGroup; + } + + #endregion + } +} diff --git a/TimetableDesigner/ViewModels/Models/ClassViewModel.cs b/TimetableDesigner/ViewModels/Models/ClassVM.cs similarity index 59% rename from TimetableDesigner/ViewModels/Models/ClassViewModel.cs rename to TimetableDesigner/ViewModels/Models/ClassVM.cs index 1fdfc03..69e7624 100644 --- a/TimetableDesigner/ViewModels/Models/ClassViewModel.cs +++ b/TimetableDesigner/ViewModels/Models/ClassVM.cs @@ -1,16 +1,20 @@ using System; using System.Collections.Generic; using System.ComponentModel.Design; +using System.Diagnostics; +using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using TimetableDesigner.Core; +using TimetableDesigner.Customs; using TimetableDesigner.Services; using TimetableDesigner.Services.Project; +using TimetableDesigner.ViewModels.Models.Base; namespace TimetableDesigner.ViewModels.Models { - public class ClassViewModel : BaseModelViewModel + public class ClassVM : ObservableObject, IModelVM { #region FIELDS @@ -38,7 +42,7 @@ namespace TimetableDesigner.ViewModels.Models } } } - public TeacherViewModel? Teacher + public TeacherVM? Teacher { get => _projectService.ProjectViewModel?.Teachers.Where(vm => vm.Teacher == _class.Teacher).FirstOrDefault(); set @@ -47,18 +51,22 @@ namespace TimetableDesigner.ViewModels.Models { _class.Teacher = value?.Teacher; NotifyPropertyChanged(nameof(Teacher)); + + //REFRESH: Errors + _projectService.RefreshErrors(); } } } - public IGroupViewModel? Group + public BaseGroupVM? Group { get { - if (_class.Group?.GetType() == typeof(GroupViewModel)) + + if (_class.Group?.GetType() == typeof(Group)) { return _projectService.ProjectViewModel?.Groups.Where(vm => vm.Group == _class.Group).FirstOrDefault(); } - else if (_class.Group?.GetType() == typeof(SubgroupViewModel)) + else if (_class.Group?.GetType() == typeof(Subgroup)) { return _projectService.ProjectViewModel?.Subgroups.Where(vm => vm.Subgroup == _class.Group).FirstOrDefault(); } @@ -69,14 +77,17 @@ namespace TimetableDesigner.ViewModels.Models } set { - if (_class.Group != value?.Group) + if (_class.Group != value?.BaseGroup) { - _class.Group = value?.Group; + _class.Group = value?.BaseGroup; NotifyPropertyChanged(nameof(Group)); + + //REFRESH: Errors + _projectService.RefreshErrors(); } } } - public ClassroomViewModel? Classroom + public ClassroomVM? Classroom { get => _projectService.ProjectViewModel?.Classrooms.Where(vm => vm.Classroom == _class.Classroom).FirstOrDefault(); set @@ -85,6 +96,45 @@ namespace TimetableDesigner.ViewModels.Models { _class.Classroom = value?.Classroom; NotifyPropertyChanged(nameof(Classroom)); + + //REFRESH: Errors + _projectService.RefreshErrors(); + } + } + } + public TimetableDay? Day + { + get => _class.Day; + set + { + if (value != _class.Day) + { + _class.Day = value; + NotifyPropertyChanged(nameof(Day)); + } + } + } + public TimetableSpan? Slot + { + get => _class.Slot; + set + { + if (value != _class.Slot) + { + _class.Slot = value; + NotifyPropertyChanged(nameof(Slot)); + } + } + } + public byte[] Color + { + get => _class.Color; + set + { + if (value != _class.Color) + { + _class.Color = value; + NotifyPropertyChanged(nameof(Color)); } } } @@ -95,7 +145,7 @@ namespace TimetableDesigner.ViewModels.Models #region CONSTRUCTORS - public ClassViewModel(Class @class) + public ClassVM(Class @class) { _projectService = ServiceProvider.Instance.GetService(); diff --git a/TimetableDesigner/ViewModels/Models/ClassroomViewModel.cs b/TimetableDesigner/ViewModels/Models/ClassroomVM.cs similarity index 88% rename from TimetableDesigner/ViewModels/Models/ClassroomViewModel.cs rename to TimetableDesigner/ViewModels/Models/ClassroomVM.cs index 337405e..16d516b 100644 --- a/TimetableDesigner/ViewModels/Models/ClassroomViewModel.cs +++ b/TimetableDesigner/ViewModels/Models/ClassroomVM.cs @@ -4,10 +4,12 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using TimetableDesigner.Core; +using TimetableDesigner.Customs; +using TimetableDesigner.ViewModels.Models.Base; namespace TimetableDesigner.ViewModels.Models { - public class ClassroomViewModel : BaseModelViewModel + public class ClassroomVM : ObservableObject, IModelVM, IUnitVM, IRemovableVM { #region FIELDS @@ -19,6 +21,7 @@ namespace TimetableDesigner.ViewModels.Models #region PROPERTIES + IUnit IUnitVM.Unit => _classroom; public Classroom Classroom => _classroom; public string Name @@ -76,7 +79,7 @@ namespace TimetableDesigner.ViewModels.Models #region CONSTRUCTORS - public ClassroomViewModel(Classroom classroom) + public ClassroomVM(Classroom classroom) { _classroom = classroom; } diff --git a/TimetableDesigner/ViewModels/Models/GroupViewModel.cs b/TimetableDesigner/ViewModels/Models/GroupVM.cs similarity index 53% rename from TimetableDesigner/ViewModels/Models/GroupViewModel.cs rename to TimetableDesigner/ViewModels/Models/GroupVM.cs index a64c978..73cb8b5 100644 --- a/TimetableDesigner/ViewModels/Models/GroupViewModel.cs +++ b/TimetableDesigner/ViewModels/Models/GroupVM.cs @@ -5,53 +5,40 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using TimetableDesigner.Core; +using TimetableDesigner.Customs; using TimetableDesigner.Services; using TimetableDesigner.Services.Project; +using TimetableDesigner.ViewModels.Models.Base; namespace TimetableDesigner.ViewModels.Models { - public class GroupViewModel : BaseModelViewModel, IGroupViewModel + public class GroupVM : BaseGroupVM, IRemovableVM { #region FIELDS private IProjectService _projectService; - private Group _group; - #endregion #region PROPERTIES - IGroup IGroupViewModel.Group => _group; - public Group Group => _group; + public Group Group => (Group)_baseGroup; - public string Name - { - get => _group.Name; - set - { - if (_group.Name != value) - { - _group.Name = value; - NotifyPropertyChanged(nameof(Name)); - } - } - } public string Description { - get => _group.Description; + get => Group.Description; set { - if (_group.Description != value) + if (Group.Description != value) { - _group.Description = value; + Group.Description = value; NotifyPropertyChanged(nameof(Description)); } } } - public ObservableCollection AssignedSubgroups => new ObservableCollection(_projectService.ProjectViewModel.Subgroups.Where(vm => Group.AssignedSubgroups.Contains(vm.Subgroup))); + public ObservableCollection AssignedSubgroups => new ObservableCollection(_projectService.ProjectViewModel.Subgroups.Where(vm => Group.AssignedSubgroups.Contains(vm.Subgroup))); #endregion @@ -59,10 +46,9 @@ namespace TimetableDesigner.ViewModels.Models #region CONSTRUCTORS - public GroupViewModel(Group group) + public GroupVM(Group group) : base(group) { _projectService = ServiceProvider.Instance.GetService(); - _group = group; } #endregion @@ -71,16 +57,22 @@ namespace TimetableDesigner.ViewModels.Models #region PUBLIC METHODS - public void AddSubgroup(SubgroupViewModel subgroup) + public void AddSubgroup(SubgroupVM subgroup) { Group.AssignedSubgroups.Add(subgroup.Subgroup); NotifyPropertyChanged(nameof(AssignedSubgroups)); + + //REFRESH: Errors + _projectService.RefreshErrors(); } - public void RemoveSubgroup(SubgroupViewModel subgroup) + public void RemoveSubgroup(SubgroupVM subgroup) { Group.AssignedSubgroups.Remove(subgroup.Subgroup); NotifyPropertyChanged(nameof(AssignedSubgroups)); + + //REFRESH: Errors + _projectService.RefreshErrors(); } #endregion diff --git a/TimetableDesigner/ViewModels/Models/ProjectVM.cs b/TimetableDesigner/ViewModels/Models/ProjectVM.cs new file mode 100644 index 0000000..462ee6b --- /dev/null +++ b/TimetableDesigner/ViewModels/Models/ProjectVM.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TimetableDesigner.Core; +using TimetableDesigner.Customs; +using TimetableDesigner.Services.Project; + +namespace TimetableDesigner.ViewModels.Models +{ + public class ProjectVM : ObservableObject, IModelVM + { + #region FIELDS + + private Project _project; + + #endregion + + + + #region PROPERTIES + + public Project Project => _project; + + public string Name + { + get => Project.Name; + set + { + if (Project.Name != value) + { + Project.Name = value; + NotifyPropertyChanged(nameof(Name)); + } + } + } + public string Author + { + get => Project.Author; + set + { + if (Project.Author != value) + { + Project.Author = value; + NotifyPropertyChanged(nameof(Author)); + } + } + } + public string Description + { + get => Project.Description; + set + { + if (Project.Description != value) + { + Project.Description = value; + NotifyPropertyChanged(nameof(Description)); + } + } + } + public TimetableTemplateVM TimetableTemplate { get; set; } + public ObservableCollection Classrooms { get; set; } + public ObservableCollection Teachers { get; set; } + public ObservableCollection Groups { get; set; } + public ObservableCollection Subgroups { get; set; } + public ObservableCollection Classes { get; set; } + + #endregion + + + + #region CONSTRUCTORS + + public ProjectVM(Project project) + { + _project = project; + + TimetableTemplate = new TimetableTemplateVM(_project.TimetableTemplate); + + Classrooms = new ObservableCollection(_project.Classrooms.Select(item => new ClassroomVM(item))); + Classrooms.CollectionChanged += Classrooms_CollectionChanged; + + Teachers = new ObservableCollection(_project.Teachers.Select(item => new TeacherVM(item))); + Teachers.CollectionChanged += Teachers_CollectionChanged; + + Groups = new ObservableCollection(_project.Groups.Select(item => new GroupVM(item))); + Groups.CollectionChanged += Groups_CollectionChanged; + + Subgroups = new ObservableCollection(_project.Subgroups.Select(item => new SubgroupVM(item))); + Subgroups.CollectionChanged += Subgroups_CollectionChanged; + + Classes = new ObservableCollection(_project.Classes.Select(item => new ClassVM(item))); + Classes.CollectionChanged += Classes_CollectionChanged; + } + + #endregion + + + + #region PRIVATE METHODS + + private void Classrooms_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.OldItems is not null) + { + foreach (ClassroomVM vm in e.OldItems) + { + foreach (ClassVM cvm in Classes.Where(x => x.Classroom == vm)) + { + cvm.Classroom = null; + } + _project.Classrooms.Remove(vm.Classroom); + } + } + + if (e.NewItems is not null) + { + foreach (ClassroomVM vm in e.NewItems) + { + _project.Classrooms.Add(vm.Classroom); + } + } + } + + private void Teachers_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.OldItems is not null) + { + foreach (TeacherVM vm in e.OldItems) + { + foreach (ClassVM cvm in Classes.Where(x => x.Teacher == vm)) + { + cvm.Teacher = null; + } + _project.Teachers.Remove(vm.Teacher); + } + } + + if (e.NewItems is not null) + { + foreach (TeacherVM vm in e.NewItems) + { + _project.Teachers.Add(vm.Teacher); + } + } + } + + private void Groups_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.OldItems is not null) + { + foreach (GroupVM vm in e.OldItems) + { + foreach (ClassVM cvm in Classes.Where(x => x.Group == vm)) + { + cvm.Group = null; + } + _project.Groups.Remove(vm.Group); + } + } + + if (e.NewItems is not null) + { + foreach (GroupVM vm in e.NewItems) + { + _project.Groups.Add(vm.Group); + } + } + } + + private void Subgroups_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.OldItems is not null) + { + foreach (SubgroupVM vm in e.OldItems) + { + foreach (ClassVM cvm in Classes.Where(x => x.Group == vm)) + { + cvm.Group = null; + } + foreach (GroupVM gvm in Groups.Where(x => x.AssignedSubgroups.Contains(vm))) + { + gvm.RemoveSubgroup(vm); + } + _project.Subgroups.Remove(vm.Subgroup); + } + } + + if (e.NewItems is not null) + { + foreach (SubgroupVM vm in e.NewItems) + { + _project.Subgroups.Add(vm.Subgroup); + } + } + } + + private void Classes_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.OldItems is not null) + { + foreach (ClassVM vm in e.OldItems) + { + _project.Classes.Remove(vm.Class); + } + } + + if (e.NewItems is not null) + { + foreach (ClassVM vm in e.NewItems) + { + _project.Classes.Add(vm.Class); + } + } + } + + #endregion + } +} diff --git a/TimetableDesigner/ViewModels/Models/ProjectViewModel.cs b/TimetableDesigner/ViewModels/Models/ProjectViewModel.cs deleted file mode 100644 index 5046821..0000000 --- a/TimetableDesigner/ViewModels/Models/ProjectViewModel.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TimetableDesigner.Core; - -namespace TimetableDesigner.ViewModels.Models -{ - public class ProjectViewModel : BaseModelViewModel - { - #region FIELDS - - private Project _project; - - #endregion - - - - #region PROPERTIES - - public Project Project => _project; - - public string Name - { - get => Project.Name; - set - { - if (Project.Name != value) - { - Project.Name = value; - NotifyPropertyChanged(nameof(Name)); - } - } - } - public string Author - { - get => Project.Author; - set - { - if (Project.Author != value) - { - Project.Author = value; - NotifyPropertyChanged(nameof(Author)); - } - } - } - public string Description - { - get => Project.Description; - set - { - if (Project.Description != value) - { - Project.Description = value; - NotifyPropertyChanged(nameof(Description)); - } - } - } - public TimetableTemplateViewModel TimetableTemplate { get; set; } - public ObservableCollection Classrooms { get; set; } - public ObservableCollection Teachers { get; set; } - public ObservableCollection Groups { get; set; } - public ObservableCollection Subgroups { get; set; } - - #endregion - - - - #region CONSTRUCTORS - - public ProjectViewModel(Project project) - { - _project = project; - - TimetableTemplate = new TimetableTemplateViewModel(_project.TimetableTemplate); - - Classrooms = new ObservableCollection(); - Classrooms.CollectionChanged += Classrooms_CollectionChanged; - - Teachers = new ObservableCollection(); - Teachers.CollectionChanged += Teachers_CollectionChanged; - - Groups = new ObservableCollection(); - Groups.CollectionChanged += Groups_CollectionChanged; - - Subgroups = new ObservableCollection(); - Subgroups.CollectionChanged += Subgroups_CollectionChanged; - } - - #endregion - - - - #region PRIVATE METHODS - - private void Classrooms_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - IList? added = e.NewItems as IList; - IList? removed = e.OldItems as IList; - - if (removed is not null) - { - foreach (ClassroomViewModel vm in removed) - { - _project.Classrooms.Remove(vm.Classroom); - } - } - - if (added is not null) - { - foreach (ClassroomViewModel vm in added) - { - _project.Classrooms.Add(vm.Classroom); - } - } - } - - private void Teachers_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - IList? added = e.NewItems as IList; - IList? removed = e.OldItems as IList; - - if (removed is not null) - { - foreach (TeacherViewModel vm in removed) - { - _project.Teachers.Remove(vm.Teacher); - } - } - - if (added is not null) - { - foreach (TeacherViewModel vm in added) - { - _project.Teachers.Add(vm.Teacher); - } - } - } - - private void Groups_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - IList? added = e.NewItems as IList; - IList? removed = e.OldItems as IList; - - if (removed is not null) - { - foreach (GroupViewModel vm in removed) - { - _project.Groups.Remove(vm.Group); - } - } - - if (added is not null) - { - foreach (GroupViewModel vm in added) - { - _project.Groups.Add(vm.Group); - } - } - } - - private void Subgroups_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - IList? added = e.NewItems as IList; - IList? removed = e.OldItems as IList; - - if (removed is not null) - { - foreach (SubgroupViewModel vm in removed) - { - _project.Subgroups.Remove(vm.Subgroup); - } - } - - if (added is not null) - { - foreach (SubgroupViewModel vm in added) - { - _project.Subgroups.Add(vm.Subgroup); - } - } - } - - #endregion - } -} diff --git a/TimetableDesigner/ViewModels/Models/SubgroupVM.cs b/TimetableDesigner/ViewModels/Models/SubgroupVM.cs new file mode 100644 index 0000000..91c649a --- /dev/null +++ b/TimetableDesigner/ViewModels/Models/SubgroupVM.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TimetableDesigner.Core; +using TimetableDesigner.Customs; +using TimetableDesigner.ViewModels.Models.Base; + +namespace TimetableDesigner.ViewModels.Models +{ + public class SubgroupVM : BaseGroupVM, IRemovableVM + { + #region PROPERTIES + + public Subgroup Subgroup => (Subgroup)_baseGroup; + + #endregion + + + + #region CONSTRUCTORS + + public SubgroupVM(Subgroup subgroup) : base(subgroup) + { } + + #endregion + } +} diff --git a/TimetableDesigner/ViewModels/Models/SubgroupViewModel.cs b/TimetableDesigner/ViewModels/Models/SubgroupViewModel.cs deleted file mode 100644 index 3f6ac8e..0000000 --- a/TimetableDesigner/ViewModels/Models/SubgroupViewModel.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TimetableDesigner.Core; - -namespace TimetableDesigner.ViewModels.Models -{ - public class SubgroupViewModel : BaseModelViewModel, IGroupViewModel - { - #region FIELDS - - private Subgroup _subgroup; - - #endregion - - - - #region PROPERTIES - - IGroup IGroupViewModel.Group => _subgroup; - public Subgroup Subgroup => _subgroup; - - public string Name - { - get => _subgroup.Name; - set - { - if (_subgroup.Name != value) - { - _subgroup.Name = value; - NotifyPropertyChanged(nameof(Name)); - } - } - } - - #endregion - - - - #region CONSTRUCTORS - - public SubgroupViewModel(Subgroup subgroup) - { - _subgroup = subgroup; - } - - #endregion - } -} diff --git a/TimetableDesigner/ViewModels/Models/TeacherViewModel.cs b/TimetableDesigner/ViewModels/Models/TeacherVM.cs similarity index 78% rename from TimetableDesigner/ViewModels/Models/TeacherViewModel.cs rename to TimetableDesigner/ViewModels/Models/TeacherVM.cs index 77e207d..e7a8bf5 100644 --- a/TimetableDesigner/ViewModels/Models/TeacherViewModel.cs +++ b/TimetableDesigner/ViewModels/Models/TeacherVM.cs @@ -6,13 +6,16 @@ using System.Text; using System.Threading.Tasks; using TimetableDesigner.Core; using TimetableDesigner.Customs; +using TimetableDesigner.Services.Project; +using TimetableDesigner.ViewModels.Models.Base; namespace TimetableDesigner.ViewModels.Models { - public class TeacherViewModel : BaseModelViewModel + public class TeacherVM : ObservableObject, IModelVM, IUnitVM, IRemovableVM { #region FIELDS + private IProjectService _projectService; private Teacher _teacher; #endregion @@ -21,6 +24,7 @@ namespace TimetableDesigner.ViewModels.Models #region PROPERTIES + IUnit IUnitVM.Unit => _teacher; public Teacher Teacher => _teacher; public string Name @@ -55,8 +59,9 @@ namespace TimetableDesigner.ViewModels.Models #region CONSTRUCTORS - public TeacherViewModel(Teacher teacher) + public TeacherVM(Teacher teacher) { + _projectService = ServiceProvider.Instance.GetService(); _teacher = teacher; } @@ -72,6 +77,9 @@ namespace TimetableDesigner.ViewModels.Models { Teacher.AvailabilityHours.Add(day, new TimetableSpanCollection()); NotifyPropertyChanged(nameof(AvailabilityHours)); + + //REFRESH: Errors + _projectService.RefreshErrors(); } } @@ -79,6 +87,9 @@ namespace TimetableDesigner.ViewModels.Models { Teacher.AvailabilityHours.Remove(day); NotifyPropertyChanged(nameof(AvailabilityHours)); + + //REFRESH: Errors + _projectService.RefreshErrors(); } public void AddHours(TimetableDay day, TimetableSpan hours) @@ -87,6 +98,9 @@ namespace TimetableDesigner.ViewModels.Models { Teacher.AvailabilityHours[day].Add(hours); NotifyPropertyChanged(nameof(AvailabilityHours)); + + //REFRESH: Errors + _projectService.RefreshErrors(); } } @@ -96,6 +110,9 @@ namespace TimetableDesigner.ViewModels.Models { Teacher.AvailabilityHours[day].Remove(hours); NotifyPropertyChanged(nameof(AvailabilityHours)); + + //REFRESH: Errors + _projectService.RefreshErrors(); } } diff --git a/TimetableDesigner/ViewModels/Models/TimetableTemplateViewModel.cs b/TimetableDesigner/ViewModels/Models/TimetableTemplateVM.cs similarity index 79% rename from TimetableDesigner/ViewModels/Models/TimetableTemplateViewModel.cs rename to TimetableDesigner/ViewModels/Models/TimetableTemplateVM.cs index 4e14e68..e82f4c2 100644 --- a/TimetableDesigner/ViewModels/Models/TimetableTemplateViewModel.cs +++ b/TimetableDesigner/ViewModels/Models/TimetableTemplateVM.cs @@ -5,10 +5,11 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using TimetableDesigner.Core; +using TimetableDesigner.Customs; namespace TimetableDesigner.ViewModels.Models { - public class TimetableTemplateViewModel : BaseModelViewModel + public class TimetableTemplateVM : ObservableObject, IModelVM { #region FIELDS @@ -31,7 +32,7 @@ namespace TimetableDesigner.ViewModels.Models #region CONSTRUCTORS - public TimetableTemplateViewModel(TimetableTemplate timetableTemplate) + public TimetableTemplateVM(TimetableTemplate timetableTemplate) { _timetableTemplate = timetableTemplate; } @@ -44,25 +45,25 @@ namespace TimetableDesigner.ViewModels.Models public void AddDay(TimetableDay day) { - _timetableTemplate.AddDay(day); + _timetableTemplate.Days.Add(day); NotifyPropertyChanged(nameof(Days)); } public void RemoveDay(TimetableDay day) { - _timetableTemplate.RemoveDay(day); + _timetableTemplate.Days.Remove(day); NotifyPropertyChanged(nameof(Days)); } public void AddSlot(TimetableSpan slot) { - _timetableTemplate.AddSlot(slot); + _timetableTemplate.Slots.Add(slot); NotifyPropertyChanged(nameof(Slots)); } public void RemoveSlot(TimetableSpan slot) { - _timetableTemplate.RemoveSlot(slot); + _timetableTemplate.Slots.Remove(slot); NotifyPropertyChanged(nameof(Slots)); } diff --git a/TimetableDesigner/ViewModels/Views/ClassroomEditViewModel.cs b/TimetableDesigner/ViewModels/Views/ClassroomEditorViewVM.cs similarity index 79% rename from TimetableDesigner/ViewModels/Views/ClassroomEditViewModel.cs rename to TimetableDesigner/ViewModels/Views/ClassroomEditorViewVM.cs index 9e761ef..670052a 100644 --- a/TimetableDesigner/ViewModels/Views/ClassroomEditViewModel.cs +++ b/TimetableDesigner/ViewModels/Views/ClassroomEditorViewVM.cs @@ -9,14 +9,15 @@ using TimetableDesigner.Properties; using TimetableDesigner.Services; using TimetableDesigner.Services.TabNavigation; using TimetableDesigner.ViewModels.Models; +using TimetableDesigner.ViewModels.Models.Base; namespace TimetableDesigner.ViewModels.Views { - public class ClassroomEditViewModel : BaseViewViewModel + public class ClassroomEditorViewVM : ObservableObject, IViewVM, IUnitEditorViewVM { #region FIELDS - private ClassroomViewModel _classroom; + private ClassroomVM _classroom; #endregion @@ -24,7 +25,8 @@ namespace TimetableDesigner.ViewModels.Views #region PROPERTIES - public ClassroomViewModel Classroom + IUnitVM IUnitEditorViewVM.Unit => Classroom; + public ClassroomVM Classroom { get => _classroom; set @@ -62,10 +64,10 @@ namespace TimetableDesigner.ViewModels.Views #region CONSTRUCTORS - public ClassroomEditViewModel() : this(new ClassroomViewModel(new Classroom())) + public ClassroomEditorViewVM() : this(new ClassroomVM(new Classroom())) { } - public ClassroomEditViewModel(ClassroomViewModel classroom) + public ClassroomEditorViewVM(ClassroomVM classroom) { _classroom = classroom; } diff --git a/TimetableDesigner/ViewModels/Views/GroupEditViewModel.cs b/TimetableDesigner/ViewModels/Views/GroupEditorViewVM.cs similarity index 80% rename from TimetableDesigner/ViewModels/Views/GroupEditViewModel.cs rename to TimetableDesigner/ViewModels/Views/GroupEditorViewVM.cs index 16e06f0..cd2dba6 100644 --- a/TimetableDesigner/ViewModels/Views/GroupEditViewModel.cs +++ b/TimetableDesigner/ViewModels/Views/GroupEditorViewVM.cs @@ -14,17 +14,18 @@ using System.Windows.Input; using TimetableDesigner.Commands; using System.Diagnostics; using TimetableDesigner.Services.MessageBox; +using TimetableDesigner.ViewModels.Models.Base; namespace TimetableDesigner.ViewModels.Views { - public class GroupEditViewModel : BaseViewViewModel + public class GroupEditorViewVM : ObservableObject, IViewVM, IUnitEditorViewVM { #region FIELDS private IProjectService _projectService; private IMessageBoxService _messageBoxService; - private GroupViewModel _group; + private GroupVM _group; private string _newSubgroupName; @@ -34,7 +35,8 @@ namespace TimetableDesigner.ViewModels.Views #region PROPERTIES - public GroupViewModel Group + IUnitVM IUnitEditorViewVM.Unit => Group; + public GroupVM Group { get => _group; set @@ -66,7 +68,7 @@ namespace TimetableDesigner.ViewModels.Views } } - public ObservableDictionary Subgroups => new ObservableDictionary(_projectService.ProjectViewModel.Subgroups.ToDictionary(sg => sg, Group.AssignedSubgroups.Contains)); + public ObservableDictionary Subgroups => new ObservableDictionary(_projectService.ProjectViewModel.Subgroups.ToDictionary(sg => sg, Group.AssignedSubgroups.Contains)); public string NewSubgroupName { @@ -91,10 +93,10 @@ namespace TimetableDesigner.ViewModels.Views #region CONSTRUCTORS - public GroupEditViewModel() : this(new GroupViewModel(new Group())) + public GroupEditorViewVM() : this(new GroupVM(new Group())) { } - public GroupEditViewModel(GroupViewModel group) + public GroupEditorViewVM(GroupVM group) { _projectService = ServiceProvider.Instance.GetService(); _messageBoxService = ServiceProvider.Instance.GetService(); @@ -103,8 +105,8 @@ namespace TimetableDesigner.ViewModels.Views _newSubgroupName = string.Empty; AddSubgroupCommand = new RelayCommand(args => AddSubgroup()); - EditSubgroupAssignmentCommand = new RelayCommand(EditSubgroupAssignment); - DeleteSubgroupCommand = new RelayCommand(DeleteSubgroup); + EditSubgroupAssignmentCommand = new RelayCommand(EditSubgroupAssignment); + DeleteSubgroupCommand = new RelayCommand(DeleteSubgroup); } #endregion @@ -119,7 +121,7 @@ namespace TimetableDesigner.ViewModels.Views { Name = NewSubgroupName }; - SubgroupViewModel subgroupViewModel = new SubgroupViewModel(subgroup); + SubgroupVM subgroupViewModel = new SubgroupVM(subgroup); _projectService.ProjectViewModel.Subgroups.Add(subgroupViewModel); Group.AddSubgroup(subgroupViewModel); @@ -128,7 +130,7 @@ namespace TimetableDesigner.ViewModels.Views NewSubgroupName = string.Empty; } - private void EditSubgroupAssignment(SubgroupViewModel subgroup) + private void EditSubgroupAssignment(SubgroupVM subgroup) { bool assigned = Subgroups[subgroup]; if (assigned) @@ -142,12 +144,12 @@ namespace TimetableDesigner.ViewModels.Views NotifyPropertyChanged(nameof(Subgroups)); } - private void DeleteSubgroup(SubgroupViewModel subgroup) + private void DeleteSubgroup(SubgroupVM subgroup) { MessageBoxQuestionResult result = _messageBoxService.ShowQuestion(Resources.GroupEdit_Message_SubgroupDelete, true); if (result == MessageBoxQuestionResult.Yes) { - foreach (GroupViewModel group in _projectService.ProjectViewModel.Groups) + foreach (GroupVM group in _projectService.ProjectViewModel.Groups) { group.RemoveSubgroup(subgroup); } diff --git a/TimetableDesigner/ViewModels/Views/MainViewModel.cs b/TimetableDesigner/ViewModels/Views/MainViewModel.cs deleted file mode 100644 index a443db7..0000000 --- a/TimetableDesigner/ViewModels/Views/MainViewModel.cs +++ /dev/null @@ -1,271 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Input; -using TimetableDesigner.Commands; -using System.Windows.Navigation; -using TimetableDesigner.Core; -using System.Windows; -using TimetableDesigner.Properties; -using System.Reflection; -using TimetableDesigner.ViewModels.Models; -using System.Windows.Data; -using TimetableDesigner.Services.MessageBox; -using System.ComponentModel.Design; -using TimetableDesigner.Services; -using TimetableDesigner.Services.TabNavigation; -using TimetableDesigner.Services.Project; -using System.Drawing; - -namespace TimetableDesigner.ViewModels.Views -{ - class MainViewModel : BaseViewViewModel - { - #region FIELDS - - private IMessageBoxService _messageBoxService; - private ITabNavigationService _tabNavigationService; - private IProjectService _projectService; - - #endregion - - - - #region PROPERTIES - - // Observable services - public IProjectService ProjectService => _projectService; - public ITabNavigationService TabNavigationService => _tabNavigationService; - - // Tabs - public ObservableCollection Tabs => _tabNavigationService.Tabs; - public TabItem SelectedTab => _tabNavigationService.SelectedTab; - - // Commands - public ICommand NewProjectCommand { get; set; } - public ICommand OpenProjectCommand { get; set; } - public ICommand ProjectSettingsCommand { get; set; } - public ICommand NewClassroomCommand { get; set; } - public ICommand EditClassroomCommand { get; set; } - public ICommand RemoveClassroomCommand { get; set; } - public ICommand NewTeacherCommand { get; set; } - public ICommand EditTeacherCommand { get; set; } - public ICommand RemoveTeacherCommand { get; set; } - public ICommand NewGroupCommand { get; set; } - public ICommand EditGroupCommand { get; set; } - public ICommand RemoveGroupCommand { get; set; } - public ICommand RemoveSubgroupCommand { get; set; } - - // Others - public string Version { get; set; } - - #endregion - - - - #region CONSTRUCTORS - - public MainViewModel() - { - _messageBoxService = ServiceProvider.Instance.GetService(); - _tabNavigationService = ServiceProvider.Instance.GetService(); - _projectService = ServiceProvider.Instance.GetService(); - - NewProjectCommand = new RelayCommand(param => NewProject()); - OpenProjectCommand = new RelayCommand(param => OpenProject()); - ProjectSettingsCommand = new RelayCommand(param => ProjectSettings()); - NewClassroomCommand = new RelayCommand(param => NewClassroom()); - EditClassroomCommand = new RelayCommand(EditClassroom); - RemoveClassroomCommand = new RelayCommand(DeleteClassroom); - NewTeacherCommand = new RelayCommand(param => NewTeacher()); - EditTeacherCommand = new RelayCommand(EditTeacher); - RemoveTeacherCommand = new RelayCommand(DeleteTeacher); - NewGroupCommand = new RelayCommand(param => NewGroup()); - EditGroupCommand = new RelayCommand(EditGroup); - RemoveGroupCommand = new RelayCommand(DeleteGroup); - RemoveSubgroupCommand = new RelayCommand(DeleteSubgroup); - - Version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - - TabItem welcomeTab = new TabItem() - { - Title = Resources.Tabs_Welcome, - ViewModel = new WelcomeViewModel() - }; - _tabNavigationService.AddAndActivate(welcomeTab); - } - - #endregion - - - - #region PRIVATE METHODS - - private void OpenProject() - { - } - - private void NewProject() - { - if (ProjectService.ProjectViewModel is not null) - { - MessageBoxQuestionResult result = _messageBoxService.ShowQuestion(Resources.Main_Message_SaveCurrentProject); - - switch (result) - { - case MessageBoxQuestionResult.Yes: break; - case MessageBoxQuestionResult.No: break; - default: return; - } - } - _tabNavigationService.CloseAll(); - _projectService.New(); - ProjectSettings(); - } - - private void ProjectSettings() - { - if (ProjectService.ProjectViewModel is not null) - { - TabItem projectSettingsTab = new TabItem() - { - Title = Resources.Tabs_ProjectSettings, - IsClosable = true, - ViewModel = new ProjectSettingsViewModel() - }; - _tabNavigationService.AddAndActivate(projectSettingsTab); - } - } - - private void NewClassroom() - { - if (ProjectService.ProjectViewModel is not null) - { - Classroom classroom = new Classroom() - { - Name = Resources.Global_DefaultClassroomName - }; - ClassroomViewModel classroomVM = new ClassroomViewModel(classroom); - ProjectService.ProjectViewModel.Classrooms.Add(classroomVM); - EditClassroom(classroomVM); - } - } - - private void EditClassroom(ClassroomViewModel classroomViewModel) - { - if (ProjectService.ProjectViewModel is not null) - { - TabItem classroomEditTab = new TabItem() - { - Title = $"{Resources.Tabs_ClassroomEdit}: {classroomViewModel.Name}", - IsClosable = true, - ViewModel = new ClassroomEditViewModel(classroomViewModel) - }; - _tabNavigationService.AddAndActivate(classroomEditTab); - } - } - - private void DeleteClassroom(ClassroomViewModel classroomViewModel) - { - if (ProjectService.ProjectViewModel is not null) - { - ProjectService.ProjectViewModel.Classrooms.Remove(classroomViewModel); - } - } - - private void NewTeacher() - { - if (ProjectService.ProjectViewModel is not null) - { - Teacher teacher = new Teacher() - { - Name = Resources.Global_DefaultTeacherName - }; - TeacherViewModel teacherVM = new TeacherViewModel(teacher); - ProjectService.ProjectViewModel.Teachers.Add(teacherVM); - EditTeacher(teacherVM); - } - } - - private void EditTeacher(TeacherViewModel teacherViewModel) - { - if (ProjectService.ProjectViewModel is not null) - { - TabItem teacherEditTab = new TabItem() - { - Title = $"{Resources.Tabs_TeacherEdit}: {teacherViewModel.Name}", - IsClosable = true, - ViewModel = new TeacherEditViewModel(teacherViewModel) - }; - _tabNavigationService.AddAndActivate(teacherEditTab); - } - } - - private void DeleteTeacher(TeacherViewModel teacherViewModel) - { - if (ProjectService.ProjectViewModel is not null) - { - ProjectService.ProjectViewModel.Teachers.Remove(teacherViewModel); - } - } - - private void NewGroup() - { - if (ProjectService.ProjectViewModel is not null) - { - Group group = new Group() - { - Name = Resources.Global_DefaultGroupName - }; - GroupViewModel groupVM = new GroupViewModel(group); - ProjectService.ProjectViewModel.Groups.Add(groupVM); - EditGroup(groupVM); - } - } - - private void EditGroup(GroupViewModel groupViewModel) - { - if (ProjectService.ProjectViewModel is not null) - { - TabItem groupEditTab = new TabItem() - { - Title = $"{Resources.Tabs_GroupEdit}: {groupViewModel.Name}", - IsClosable = true, - ViewModel = new GroupEditViewModel(groupViewModel) - }; - _tabNavigationService.AddAndActivate(groupEditTab); - } - } - - private void DeleteGroup(GroupViewModel groupViewModel) - { - if (ProjectService.ProjectViewModel is not null) - { - ProjectService.ProjectViewModel.Groups.Remove(groupViewModel); - } - } - - private void DeleteSubgroup(SubgroupViewModel subgroupViewModel) - { - if (ProjectService.ProjectViewModel is not null) - { - MessageBoxQuestionResult result = _messageBoxService.ShowQuestion(Resources.Main_Treeview_Subgroups_Message_Remove, true); - if (result == MessageBoxQuestionResult.Yes) - { - foreach (GroupViewModel group in ProjectService.ProjectViewModel.Groups) - { - group.RemoveSubgroup(subgroupViewModel); - } - _projectService.ProjectViewModel.Subgroups.Remove(subgroupViewModel); - } - } - } - - #endregion - } -} diff --git a/TimetableDesigner/ViewModels/Views/MainWindowVM.cs b/TimetableDesigner/ViewModels/Views/MainWindowVM.cs new file mode 100644 index 0000000..e9fd846 --- /dev/null +++ b/TimetableDesigner/ViewModels/Views/MainWindowVM.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using TimetableDesigner.Commands; +using System.Windows.Navigation; +using TimetableDesigner.Core; +using System.Windows; +using TimetableDesigner.Properties; +using System.Reflection; +using TimetableDesigner.ViewModels.Models; +using System.Windows.Data; +using TimetableDesigner.Services.MessageBox; +using System.ComponentModel.Design; +using TimetableDesigner.Services; +using TimetableDesigner.Services.TabNavigation; +using TimetableDesigner.Services.Project; +using System.Drawing; +using TimetableDesigner.ViewModels.Models.Base; +using System.Collections; +using TimetableDesigner.Customs; +using Microsoft.Win32; +using System.Windows.Forms; +using TimetableDesigner.Services.FileDialog; +using TimetableDesigner.Services.Scheduler; +using static TimetableDesigner.ViewModels.Views.TimetableEditorViewVM; + +namespace TimetableDesigner.ViewModels.Views +{ + class MainWindowVM : ObservableObject, IViewVM + { + #region FIELDS + + private IMessageBoxService _messageBoxService; + private IFileDialogService _fileDialogService; + private ITabNavigationService _tabNavigationService; + private IProjectService _projectService; + private ISchedulerService _schedulerService; + + private IDictionary> _projectFileTypes = new Dictionary>() + { + { Resources.Global_ProjectFiletypeDescription, new List() { "ttdp" } } + }; + + #endregion + + + + #region PROPERTIES + + // Observable services + public IProjectService ProjectService => _projectService; + public ITabNavigationService TabNavigationService => _tabNavigationService; + + // Tabs + public ObservableCollection Tabs => _tabNavigationService.Tabs; + public TabItem SelectedTab => _tabNavigationService.SelectedTab; + + // Commands + public ICommand NewProjectCommand { get; set; } + public ICommand OpenProjectCommand { get; set; } + public ICommand OpenRecentProjectCommand { get; set; } + public ICommand SaveProjectCommand { get; set; } + public ICommand SaveAsProjectCommand { get; set; } + public ICommand ProjectSettingsCommand { get; set; } + public ICommand NewClassroomCommand { get; set; } + public ICommand NewTeacherCommand { get; set; } + public ICommand NewGroupCommand { get; set; } + public ICommand EditClassroomCommand { get; set; } + public ICommand EditTeacherCommand { get; set; } + public ICommand EditGroupCommand { get; set; } + public ICommand RemoveClassroomCommand { get; set; } + public ICommand RemoveTeacherCommand { get; set; } + public ICommand RemoveGroupCommand { get; set; } + public ICommand RemoveSubgroupCommand { get; set; } + public ICommand EditTimetableCommand { get; set; } + public ICommand AutoScheduleCommand { get; set; } + + // Others + public string? Version { get; set; } + + #endregion + + + + #region CONSTRUCTORS + + public MainWindowVM() + { + _messageBoxService = ServiceProvider.Instance.GetService(); + _fileDialogService = ServiceProvider.Instance.GetService(); + _tabNavigationService = ServiceProvider.Instance.GetService(); + _projectService = ServiceProvider.Instance.GetService(); + _schedulerService = ServiceProvider.Instance.GetService(); + + NewProjectCommand = new RelayCommand(param => NewProject()); + OpenProjectCommand = new RelayCommand(param => OpenProject()); + OpenRecentProjectCommand = new RelayCommand(OpenProject); + SaveProjectCommand = new RelayCommand(param => SaveProject()); + SaveAsProjectCommand = new RelayCommand(param => SaveAsProject()); + ProjectSettingsCommand = new RelayCommand(param => ProjectSettings()); + NewClassroomCommand = new RelayCommand(param => NewClassroom()); + NewTeacherCommand = new RelayCommand(param => NewTeacher()); + NewGroupCommand = new RelayCommand(param => NewGroup()); + EditClassroomCommand = new RelayCommand(EditClassroom); + EditTeacherCommand = new RelayCommand(EditTeacher); + EditGroupCommand = new RelayCommand(EditGroup); + RemoveClassroomCommand = new RelayCommand(RemoveClassroom); + RemoveTeacherCommand = new RelayCommand(RemoveTeacher); + RemoveGroupCommand = new RelayCommand(RemoveGroup); + RemoveSubgroupCommand = new RelayCommand(RemoveSubgroup); + EditTimetableCommand = new RelayCommand(EditTimetable); + AutoScheduleCommand = new RelayCommand(param => AutoSchedule()); + + Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString(); + } + + #endregion + + + + #region PRIVATE METHODS + + private void NewProject() + { + if (ProjectService.ProjectViewModel is not null) + { + MessageBoxQuestionResult result = _messageBoxService.ShowQuestion(Resources.Main_Message_SaveCurrentProject); + + switch (result) + { + case MessageBoxQuestionResult.Yes: SaveProject(); break; + case MessageBoxQuestionResult.No: break; + default: return; + } + } + + _tabNavigationService.CloseAll(); + _projectService.New(); + ProjectSettings(); + } + + private void OpenProject() + { + string? path = _fileDialogService.OpenFile(_projectFileTypes); + if (path is not null) + { + OpenProject(path); + } + } + + private void OpenProject(string path) + { + if (ProjectService.ProjectViewModel is not null) + { + MessageBoxQuestionResult result = _messageBoxService.ShowQuestion(Resources.Main_Message_SaveCurrentProject); + + switch (result) + { + case MessageBoxQuestionResult.Yes: SaveProject(); break; + case MessageBoxQuestionResult.No: break; + default: return; + } + } + + _projectService.Load(path); + _tabNavigationService.CloseAll(); + } + + private void SaveProject() + { + string? path = _projectService.SavePath; + if (_projectService.SavePath is null) + { + path = _fileDialogService.SaveFile(_projectFileTypes); + } + + if (path is not null) + { + _projectService.Save(path); + } + } + private void SaveAsProject() + { + string? path = _fileDialogService.SaveFile(_projectFileTypes); + if (path is not null) + { + _projectService.Save(path); + } + } + + private void ProjectSettings() + { + if (ProjectService.ProjectViewModel is not null) + { + TabItem projectSettingsTab = new TabItem() + { + Title = Resources.Tabs_ProjectSettings, + IsClosable = true, + ViewModel = new ProjectSettingsViewVM() + }; + _tabNavigationService.AddAndActivate(projectSettingsTab); + } + } + + private void NewClassroom() + { + if (ProjectService.ProjectViewModel is not null) + { + Classroom classroom = new Classroom() + { + Name = Resources.Global_DefaultClassroomName + }; + ClassroomVM classroomVM = new ClassroomVM(classroom); + ProjectService.ProjectViewModel.Classrooms.Add(classroomVM); + EditClassroom(classroomVM); + } + } + private void NewTeacher() + { + if (ProjectService.ProjectViewModel is not null) + { + Teacher teacher = new Teacher() + { + Name = Resources.Global_DefaultTeacherName + }; + TeacherVM teacherVM = new TeacherVM(teacher); + ProjectService.ProjectViewModel.Teachers.Add(teacherVM); + EditTeacher(teacherVM); + } + } + private void NewGroup() + { + if (ProjectService.ProjectViewModel is not null) + { + Group group = new Group() + { + Name = Resources.Global_DefaultGroupName + }; + GroupVM groupVM = new GroupVM(group); + ProjectService.ProjectViewModel.Groups.Add(groupVM); + EditGroup(groupVM); + } + } + + private void EditClassroom(ClassroomVM classroom) => EditUnit(new ClassroomEditorViewVM(classroom), Resources.Tabs_ClassroomEdit); + private void EditTeacher(TeacherVM teacher) => EditUnit(new TeacherEditorViewVM(teacher), Resources.Tabs_TeacherEdit); + private void EditGroup(GroupVM group) => EditUnit(new GroupEditorViewVM(group), Resources.Tabs_GroupEdit); + private void EditUnit(IUnitEditorViewVM edit, string tabNamePrefix) + { + if (ProjectService.ProjectViewModel is not null) + { + TabItem groupEditTab = new TabItem() + { + Title = $"{tabNamePrefix}: {edit.Unit.Name}", + IsClosable = true, + ViewModel = edit + }; + _tabNavigationService.AddAndActivate(groupEditTab); + } + } + + private void RemoveClassroom(ClassroomVM classroom) => RemoveUnit(classroom, ProjectService.ProjectViewModel?.Classrooms, Resources.Main_Treeview_Classrooms_Message_Remove); + private void RemoveTeacher(TeacherVM teacher) => RemoveUnit(teacher, ProjectService.ProjectViewModel?.Teachers, Resources.Main_Treeview_Teachers_Message_Remove); + private void RemoveGroup(GroupVM group) => RemoveUnit(group, ProjectService.ProjectViewModel?.Groups, Resources.Main_Treeview_Groups_Message_Remove); + private void RemoveSubgroup(SubgroupVM subgroup) => RemoveUnit(subgroup, ProjectService.ProjectViewModel?.Subgroups, Resources.Main_Treeview_Subgroups_Message_Remove); + private void RemoveUnit(IRemovableVM unit, IList? collection, string questionMessage) + { + if (collection is not null) + { + MessageBoxQuestionResult result = _messageBoxService.ShowQuestion(questionMessage, true); + if (result == MessageBoxQuestionResult.Yes) + { + collection.Remove(unit); + _tabNavigationService.Close(_tabNavigationService.Tabs.Where(x => x.ViewModel is IUnitEditorViewVM model && model.Unit == unit)); + } + } + } + + private void EditTimetable(IUnitVM classUnit) + { + if (ProjectService.ProjectViewModel is not null) + { + TabItem timetableEditTab = new TabItem() + { + Title = $"{Resources.Tabs_TimetableEdit}: {classUnit.Name}", + IsClosable = true, + ViewModel = new TimetableEditorViewVM(classUnit) + }; + _tabNavigationService.AddAndActivate(timetableEditTab); + } + } + + private void AutoSchedule() + { + IEnumerable unscheduledClasses = _projectService.ProjectViewModel.Classes.Where(x => x.Day is null || x.Slot is null); + if (unscheduledClasses.Any()) + { + List errors = new List(); + int successCount = 0; + foreach (ClassVM @class in unscheduledClasses) + { + (TimetableDay? day, TimetableSpan? slot) = _schedulerService.Schedule(@class); + if (day is not null && slot is not null) + { + @class.Day = day; + @class.Slot = slot; + successCount++; + } + else + { + errors.Add(@class); + } + } + + //REFRESH: All editors & Errors + foreach (TimetableEditorViewVM editor in _tabNavigationService.Tabs.Select(x => x.ViewModel).OfType().Distinct()) + { + editor.RefreshClasses(RefreshMode.Scheduled | RefreshMode.Unscheduled); + } + _projectService.RefreshErrors(); + + StringBuilder sb = new StringBuilder(); + sb.Append($"{Resources.Main_Ribbon_Edit_Timetable_Autoschedule_Message_SuccessfullyScheduled}: {successCount}"); + if (errors.Any()) + { + sb.AppendLine(); + sb.AppendLine(); + sb.Append($"{Resources.Main_Ribbon_Edit_Timetable_Autoschedule_Message_FollowingClassesCouldNotBeScheduled}:"); + sb.AppendLine(); + int deleted = 0; + for (int i = 0; i < errors.Count && i < 5; i++) + { + ClassVM errorClass = errors[i]; + string unit; + string unitName; + if (errorClass.Group is not null) + { + unit = Resources.Main_Ribbon_Edit_Timetable_Autoschedule_Message_UnitGroup; + unitName = errorClass.Group.Name; + } + else if (errorClass.Teacher is not null) + { + unit = Resources.Main_Ribbon_Edit_Timetable_Autoschedule_Message_UnitTeacher; + unitName = errorClass.Teacher.Name; + } + else if (errorClass.Classroom is not null) + { + unit = Resources.Main_Ribbon_Edit_Timetable_Autoschedule_Message_UnitClassroom; + unitName = errorClass.Classroom.Name; + } + else + { + deleted++; + i--; + _projectService.ProjectViewModel.Classes.Remove(errorClass); + continue; + } + sb.Append($"- {errorClass.Name} ({unit}: {unitName})"); + sb.AppendLine(); + } + if (errors.Count - deleted > 5) + { + sb.Append($"+ {errors.Count - deleted - 5} {Resources.Main_Ribbon_Edit_Timetable_Autoschedule_Message_More}"); + } + + _messageBoxService.ShowWarning(sb.ToString()); + } + else + { + _messageBoxService.ShowInformation(sb.ToString()); + } + } + else + { + _messageBoxService.ShowInformation(Resources.Main_Ribbon_Edit_Timetable_Autoschedule_Message_NoUnscheduledClasses); + } + } + + #endregion + } +} diff --git a/TimetableDesigner/ViewModels/Views/ProjectSettingsViewModel.cs b/TimetableDesigner/ViewModels/Views/ProjectSettingsViewVM.cs similarity index 87% rename from TimetableDesigner/ViewModels/Views/ProjectSettingsViewModel.cs rename to TimetableDesigner/ViewModels/Views/ProjectSettingsViewVM.cs index 249154e..146089d 100644 --- a/TimetableDesigner/ViewModels/Views/ProjectSettingsViewModel.cs +++ b/TimetableDesigner/ViewModels/Views/ProjectSettingsViewVM.cs @@ -19,7 +19,7 @@ using TimetableDesigner.ViewModels.Models; namespace TimetableDesigner.ViewModels.Views { - public class ProjectSettingsViewModel : BaseViewViewModel + public class ProjectSettingsViewVM : ObservableObject, IViewVM { #region FIELDS @@ -37,7 +37,7 @@ namespace TimetableDesigner.ViewModels.Views #region PROPERTIES // Project - public ProjectViewModel? Project => _projectService.ProjectViewModel; + public ProjectVM? Project => _projectService.ProjectViewModel; // Fields public string NewDayName @@ -81,7 +81,7 @@ namespace TimetableDesigner.ViewModels.Views #region CONSTRUCTORS - public ProjectSettingsViewModel() + public ProjectSettingsViewVM() { _messageBoxService = ServiceProvider.Instance.GetService(); _projectService = ServiceProvider.Instance.GetService(); @@ -115,7 +115,7 @@ namespace TimetableDesigner.ViewModels.Views { if (Project is not null) { - foreach (TeacherViewModel teacher in Project.Teachers) + foreach (TeacherVM teacher in Project.Teachers) { teacher.RemoveDay(day); } @@ -137,11 +137,19 @@ namespace TimetableDesigner.ViewModels.Views try { + TimetableSpan? lastSlot = Project.TimetableTemplate.Slots.LastOrDefault(); + Project.TimetableTemplate.AddSlot(new TimetableSpan(from, to)); + double offset = 0; + if (lastSlot != null) + { + offset = (from - lastSlot.To).TotalMinutes; + } + double delta = (to - from).TotalMinutes; - DateTime newFrom = NewSlotTo.Value; - DateTime newTo = NewSlotTo.Value.AddMinutes(delta); + DateTime newFrom = NewSlotTo.Value.AddMinutes(offset); + DateTime newTo = NewSlotTo.Value.AddMinutes(offset + delta); NewSlotFrom = newFrom; NewSlotTo = newTo; } diff --git a/TimetableDesigner/ViewModels/Views/TeacherEditViewModel.cs b/TimetableDesigner/ViewModels/Views/TeacherEditorViewVM.cs similarity index 90% rename from TimetableDesigner/ViewModels/Views/TeacherEditViewModel.cs rename to TimetableDesigner/ViewModels/Views/TeacherEditorViewVM.cs index 7711d00..63ad600 100644 --- a/TimetableDesigner/ViewModels/Views/TeacherEditViewModel.cs +++ b/TimetableDesigner/ViewModels/Views/TeacherEditorViewVM.cs @@ -14,17 +14,18 @@ using TimetableDesigner.Services.MessageBox; using TimetableDesigner.Services.Project; using TimetableDesigner.Services.TabNavigation; using TimetableDesigner.ViewModels.Models; +using TimetableDesigner.ViewModels.Models.Base; namespace TimetableDesigner.ViewModels.Views { - public class TeacherEditViewModel : BaseViewViewModel + public class TeacherEditorViewVM : ObservableObject, IViewVM, IUnitEditorViewVM { #region FIELDS private IMessageBoxService _messageBoxService; private IProjectService _projectService; - private TeacherViewModel _teacher; + private TeacherVM _teacher; private TimetableDay _selectedDay; @@ -38,7 +39,8 @@ namespace TimetableDesigner.ViewModels.Views #region PROPERTIES - public TeacherViewModel Teacher + IUnitVM IUnitEditorViewVM.Unit => Teacher; + public TeacherVM Teacher { get => _teacher; set @@ -50,7 +52,7 @@ namespace TimetableDesigner.ViewModels.Views } } } - public TimetableTemplateViewModel? TimetableTemplate => _projectService.ProjectViewModel?.TimetableTemplate; + public TimetableTemplateVM? TimetableTemplate => _projectService.ProjectViewModel?.TimetableTemplate; public string Name { @@ -65,7 +67,7 @@ namespace TimetableDesigner.ViewModels.Views TabItem? tab = ServiceProvider.Instance.GetService().Tabs.Where(tab => tab.ViewModel == this).FirstOrDefault(); if (tab != null) { - tab.Title = $"{Resources.Tabs_ClassroomEdit}: {_teacher.Name}"; + tab.Title = $"{Resources.Tabs_TeacherEdit}: {_teacher.Name}"; } } } @@ -130,10 +132,10 @@ namespace TimetableDesigner.ViewModels.Views #region CONSTRUCTORS - public TeacherEditViewModel() : this(new TeacherViewModel(new Teacher())) + public TeacherEditorViewVM() : this(new TeacherVM(new Teacher())) { } - public TeacherEditViewModel(TeacherViewModel teacher) + public TeacherEditorViewVM(TeacherVM teacher) { _messageBoxService = ServiceProvider.Instance.GetService(); _projectService = ServiceProvider.Instance.GetService(); diff --git a/TimetableDesigner/ViewModels/Views/TimetableEditorViewVM.cs b/TimetableDesigner/ViewModels/Views/TimetableEditorViewVM.cs new file mode 100644 index 0000000..1c1abcf --- /dev/null +++ b/TimetableDesigner/ViewModels/Views/TimetableEditorViewVM.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TimetableDesigner.Services; +using TimetableDesigner.Services.Project; +using TimetableDesigner.ViewModels.Models.Base; +using TimetableDesigner.ViewModels.Models; +using TimetableDesigner.Customs; +using System.Collections.ObjectModel; +using System.Windows.Input; +using TimetableDesigner.Commands; +using System.Windows; +using System.Diagnostics; +using TimetableDesigner.Core; +using System.Xml.Linq; +using TimetableDesigner.Services.TabNavigation; +using System.Security.Policy; +using TimetableDesigner.Converters; +using System.Data; +using static TimetableDesigner.ViewModels.Views.TimetableEditorViewVM; +using TimetableDesigner.Services.Scheduler; + +namespace TimetableDesigner.ViewModels.Views +{ + public class TimetableEditorViewVM : ObservableObject, IViewVM + { + #region FIELDS + + private IProjectService _projectService; + private ITabNavigationService _tabNavigationService; + private ISchedulerService _schedulerService; + + private IUnitVM? _unit; + + #endregion + + + + #region PROPERTIES + + public TimetableTemplateVM TimetableTemplate => _projectService.ProjectViewModel.TimetableTemplate; + + public IUnitVM? Unit + { + get => _unit; + set + { + if (value != _unit) + { + _unit = value; + NotifyPropertyChanged(nameof(Unit)); + } + } + } + + public IEnumerable AssignedClasses => _projectService.ProjectViewModel.Classes.Where(c => c.Classroom == Unit || c.Teacher == Unit || c.Group == Unit || (Unit is GroupVM g && c.Group is SubgroupVM s && g.AssignedSubgroups.Contains(s))); + public IEnumerable ScheduledClasses => AssignedClasses.Where(c => c.Slot is not null && c.Day is not null); + public IEnumerable UnscheduledClasses => AssignedClasses.Where(c => c.Slot is null || c.Day is null); + + public ICommand AddClassCommand { get; set; } + public ICommand AutoScheduleCommand { get; set; } + public ICommand RemoveClassCommand { get; set; } + public ICommand CloneClassCommand { get; set; } + public ICommand RefreshScheduledClassesCommand { get; set; } + public ICommand RefreshUnscheduledClassesCommand { get; set; } + public ICommand RefreshAllClassesCommand { get; set; } + + #endregion + + + + #region CONSTRUCTORS + + public TimetableEditorViewVM() : this(null) + { } + + public TimetableEditorViewVM(IUnitVM? unit) + { + _projectService = ServiceProvider.Instance.GetService(); + _tabNavigationService = ServiceProvider.Instance.GetService(); + _schedulerService = ServiceProvider.Instance.GetService(); + + _unit = unit; + + AddClassCommand = new RelayCommand(arg => AddClass()); + AutoScheduleCommand = new RelayCommand(arg => AutoSchedule()); + RemoveClassCommand = new RelayCommand(RemoveClass); + CloneClassCommand = new RelayCommand(CloneClass); + RefreshScheduledClassesCommand = new RelayCommand(arg => RefreshClassesAndErrors(RefreshMode.Scheduled)); + RefreshUnscheduledClassesCommand = new RelayCommand(arg => RefreshClassesAndErrors(RefreshMode.Unscheduled)); + RefreshAllClassesCommand = new RelayCommand(arg => RefreshClassesAndErrors(RefreshMode.Scheduled | RefreshMode.Unscheduled)); + } + + #endregion + + + + #region PUBLIC METHODS + + public void RefreshClasses(RefreshMode mode) + { + if ((mode & RefreshMode.Scheduled) == RefreshMode.Scheduled) + NotifyPropertyChanged(nameof(ScheduledClasses)); + if ((mode & RefreshMode.Unscheduled) == RefreshMode.Unscheduled) + NotifyPropertyChanged(nameof(UnscheduledClasses)); + } + + #endregion + + + + #region PRIVATE METHODS + + private void AddClass() + { + Class @class = new Class() + { + Name = "New class", + Teacher = _unit is TeacherVM t ? t.Teacher : null, + Classroom = _unit is ClassroomVM c ? c.Classroom : null, + Group = _unit is BaseGroupVM g ? g.BaseGroup : null, + }; + ClassVM classVM = new ClassVM(@class); + _projectService.ProjectViewModel.Classes.Add(classVM); + + //REFRESH: Unassigned classes of this unit & Errors + RefreshClasses(RefreshMode.Unscheduled); + _projectService.RefreshErrors(); + } + + private void AutoSchedule() + { + foreach (ClassVM @class in UnscheduledClasses) + { + (TimetableDay? day, TimetableSpan? slot) = _schedulerService.Schedule(@class); + @class.Day = day; + @class.Slot = slot; + } + + //REFRESH: Scheduled & Errors + foreach (TimetableEditorViewVM editors in _tabNavigationService.Tabs.Select(x => x.ViewModel).OfType().Distinct()) + { + editors.RefreshClasses(RefreshMode.Scheduled | RefreshMode.Unscheduled); + } + _projectService.RefreshErrors(); + } + + private void RemoveClass(ClassVM @class) + { + Debug.WriteLine(@class.Name); + List units = new List(); + if (@class.Teacher is not null) + { + units.Add(@class.Teacher); + } + if (@class.Classroom is not null) + { + units.Add(@class.Classroom); + } + if (@class.Group is not null) + { + units.Add(@class.Group); + if (@class.Group is SubgroupVM sg) + { + units.AddRange(_projectService.ProjectViewModel.Groups.Where(x => x.AssignedSubgroups.Contains(sg))); + } + } + + RefreshMode refreshMode = RefreshMode.Unscheduled; + if (@class.Day is not null && @class.Slot is not null) + { + refreshMode = RefreshMode.Scheduled; + } + + _projectService.ProjectViewModel.Classes.Remove(@class); + + //REFRESH: Assigned classes of units assigned to class & Errors + foreach (TimetableEditorViewVM editors in _tabNavigationService.Tabs.Select(x => x.ViewModel).OfType().Where(x => units.Contains(x.Unit)).Distinct()) + { + editors.RefreshClasses(refreshMode); + } + _projectService.RefreshErrors(); + } + + private void CloneClass(ClassVM @class) + { + List units = new List(); + if (@class.Teacher is not null) + { + units.Add(@class.Teacher); + } + if (@class.Classroom is not null) + { + units.Add(@class.Classroom); + } + if (@class.Group is not null) + { + units.Add(@class.Group); + if (@class.Group is SubgroupVM sg) + { + units.AddRange(_projectService.ProjectViewModel.Groups.Where(x => x.AssignedSubgroups.Contains(sg))); + } + } + + RefreshMode refreshMode = RefreshMode.Unscheduled; + if (@class.Day is not null && @class.Slot is not null) + { + refreshMode = RefreshMode.Scheduled; + } + + Class newClass = new Class() + { + Name = @class.Name, + Teacher = @class.Teacher?.Teacher, + Classroom = @class.Classroom?.Classroom, + Group = @class.Group?.BaseGroup, + Day = @class.Day, + Slot = @class.Slot, + Color = @class.Color, + }; + ClassVM classVM = new ClassVM(newClass); + _projectService.ProjectViewModel.Classes.Add(classVM); + + //REFRESH: Assigned classes of units assigned to class & Errors + foreach (TimetableEditorViewVM editors in _tabNavigationService.Tabs.Select(x => x.ViewModel).OfType().Where(x => units.Contains(x.Unit)).Distinct()) + { + editors.RefreshClasses(refreshMode); + } + _projectService.RefreshErrors(); + } + + private void RefreshClassesAndErrors(RefreshMode mode) + { + RefreshClasses(mode); + _projectService.RefreshErrors(); + } + + #endregion + + + + #region ENUMS + + [Flags] + public enum RefreshMode + { + Scheduled, + Unscheduled + } + + #endregion + } +} diff --git a/TimetableDesigner/ViewModels/Views/WelcomeViewModel.cs b/TimetableDesigner/ViewModels/Views/WelcomeViewVM.cs similarity index 75% rename from TimetableDesigner/ViewModels/Views/WelcomeViewModel.cs rename to TimetableDesigner/ViewModels/Views/WelcomeViewVM.cs index 809b737..cf55536 100644 --- a/TimetableDesigner/ViewModels/Views/WelcomeViewModel.cs +++ b/TimetableDesigner/ViewModels/Views/WelcomeViewVM.cs @@ -7,11 +7,11 @@ using TimetableDesigner.Customs; namespace TimetableDesigner.ViewModels.Views { - public class WelcomeViewModel : BaseViewViewModel + public class WelcomeViewVM : ObservableObject, IViewVM { #region CONSTRUCTORS - public WelcomeViewModel() + public WelcomeViewVM() { } #endregion diff --git a/TimetableDesigner/Views/ClassroomEditView.xaml b/TimetableDesigner/Views/ClassroomEditorView.xaml similarity index 96% rename from TimetableDesigner/Views/ClassroomEditView.xaml rename to TimetableDesigner/Views/ClassroomEditorView.xaml index aee07bc..87f3359 100644 --- a/TimetableDesigner/Views/ClassroomEditView.xaml +++ b/TimetableDesigner/Views/ClassroomEditorView.xaml @@ -1,4 +1,4 @@ - - + diff --git a/TimetableDesigner/Views/ClassroomEditView.xaml.cs b/TimetableDesigner/Views/ClassroomEditorView.xaml.cs similarity index 83% rename from TimetableDesigner/Views/ClassroomEditView.xaml.cs rename to TimetableDesigner/Views/ClassroomEditorView.xaml.cs index 82bbe7f..535d4dd 100644 --- a/TimetableDesigner/Views/ClassroomEditView.xaml.cs +++ b/TimetableDesigner/Views/ClassroomEditorView.xaml.cs @@ -15,9 +15,9 @@ using System.Windows.Shapes; namespace TimetableDesigner.Views { - public partial class ClassroomEditView : UserControl + public partial class ClassroomEditorView : UserControl { - public ClassroomEditView() + public ClassroomEditorView() { InitializeComponent(); } diff --git a/TimetableDesigner/Views/GroupEditView.xaml b/TimetableDesigner/Views/GroupEditorView.xaml similarity index 97% rename from TimetableDesigner/Views/GroupEditView.xaml rename to TimetableDesigner/Views/GroupEditorView.xaml index 9f91fec..f2baabd 100644 --- a/TimetableDesigner/Views/GroupEditView.xaml +++ b/TimetableDesigner/Views/GroupEditorView.xaml @@ -1,4 +1,4 @@ - - + diff --git a/TimetableDesigner/Views/GroupEditView.xaml.cs b/TimetableDesigner/Views/GroupEditorView.xaml.cs similarity index 84% rename from TimetableDesigner/Views/GroupEditView.xaml.cs rename to TimetableDesigner/Views/GroupEditorView.xaml.cs index 6cc425e..a850677 100644 --- a/TimetableDesigner/Views/GroupEditView.xaml.cs +++ b/TimetableDesigner/Views/GroupEditorView.xaml.cs @@ -15,9 +15,9 @@ using System.Windows.Shapes; namespace TimetableDesigner.Views { - public partial class GroupEditView : UserControl + public partial class GroupEditorView : UserControl { - public GroupEditView() + public GroupEditorView() { InitializeComponent(); } diff --git a/TimetableDesigner/Views/MainWindow.xaml b/TimetableDesigner/Views/MainWindow.xaml index 824ae8a..f27c78b 100644 --- a/TimetableDesigner/Views/MainWindow.xaml +++ b/TimetableDesigner/Views/MainWindow.xaml @@ -13,12 +13,14 @@ - + + + @@ -27,14 +29,30 @@ - - - - + + + + + + + + + + + + + + + - - + + @@ -54,6 +72,13 @@ + + + + + @@ -61,125 +86,134 @@ - - - - - - - - - - - + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -208,5 +242,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TimetableDesigner/Views/ProjectSettingsView.xaml b/TimetableDesigner/Views/ProjectSettingsView.xaml index 23c0356..f9196fc 100644 --- a/TimetableDesigner/Views/ProjectSettingsView.xaml +++ b/TimetableDesigner/Views/ProjectSettingsView.xaml @@ -9,42 +9,46 @@ mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + - - - + + + diff --git a/TimetableDesigner/Views/TeacherEditView.xaml b/TimetableDesigner/Views/TeacherEditView.xaml deleted file mode 100644 index 8cc4bdf..0000000 --- a/TimetableDesigner/Views/TeacherEditView.xaml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TimetableDesigner/Views/TeacherEditorView.xaml b/TimetableDesigner/Views/TeacherEditorView.xaml new file mode 100644 index 0000000..8c81168 --- /dev/null +++ b/TimetableDesigner/Views/TeacherEditorView.xaml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TimetableDesigner/Views/TeacherEditView.xaml.cs b/TimetableDesigner/Views/TeacherEditorView.xaml.cs similarity index 84% rename from TimetableDesigner/Views/TeacherEditView.xaml.cs rename to TimetableDesigner/Views/TeacherEditorView.xaml.cs index b923b69..fd5ad40 100644 --- a/TimetableDesigner/Views/TeacherEditView.xaml.cs +++ b/TimetableDesigner/Views/TeacherEditorView.xaml.cs @@ -15,9 +15,9 @@ using System.Windows.Shapes; namespace TimetableDesigner.Views { - public partial class TeacherEditView : UserControl + public partial class TeacherEditorView : UserControl { - public TeacherEditView() + public TeacherEditorView() { InitializeComponent(); } diff --git a/TimetableDesigner/Views/TimetableEditorView.xaml b/TimetableDesigner/Views/TimetableEditorView.xaml new file mode 100644 index 0000000..bed766e --- /dev/null +++ b/TimetableDesigner/Views/TimetableEditorView.xaml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TimetableDesigner/Views/TimetableEditorView.xaml.cs b/TimetableDesigner/Views/TimetableEditorView.xaml.cs new file mode 100644 index 0000000..99fae86 --- /dev/null +++ b/TimetableDesigner/Views/TimetableEditorView.xaml.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Xml.Linq; +using TimetableDesigner.Controls; +using TimetableDesigner.Services.Project; +using TimetableDesigner.Services.TabNavigation; +using TimetableDesigner.ViewModels.Models; +using TimetableDesigner.ViewModels.Views; + +namespace TimetableDesigner.Views +{ + public partial class TimetableEditorView : UserControl + { + #region FIELDS + + private ITabNavigationService _tabNavigationService; + private IProjectService _projectService; + + #endregion + + + + #region CONSTRUCTORS + + public TimetableEditorView() + { + _tabNavigationService = ServiceProvider.Instance.GetService(); + _projectService = ServiceProvider.Instance.GetService(); + InitializeComponent(); + } + + #endregion + + private void MouseMoveEvent(object sender, MouseEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed && sender is ClassControl control && !control.EditButton.IsChecked.Value) + { + DragDrop.DoDragDrop((DependencyObject)sender, new DataObject(DataFormats.Serializable, sender), DragDropEffects.Move); + } + } + + private void ItemsControl_Drop(object sender, DragEventArgs e) + { + object item = e.Data.GetData(DataFormats.Serializable); + if (item is UIElement element) + { + DynamicGrid.SetRow(element, -1); + DynamicGrid.SetColumn(element, -1); + } + } + } +} diff --git a/TimetableDesigner/Views/WelcomeView.xaml b/TimetableDesigner/Views/WelcomeView.xaml index 484390f..2b51a1d 100644 --- a/TimetableDesigner/Views/WelcomeView.xaml +++ b/TimetableDesigner/Views/WelcomeView.xaml @@ -4,13 +4,11 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:TimetableDesigner.ViewModels.Views" - xmlns:c="clr-namespace:TimetableDesigner.Controls" mc:Ignorable="d"> - +