From ecda3c6b8c2b097d02b326703d3dadf467209395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6ttger?= <sebastian.boettger@stud.hs-bochum.de> Date: Sun, 31 Mar 2024 21:33:32 +0000 Subject: [PATCH] Release2.0 --- .gitignore | 23 + README.md | 4 +- .../classDiagramLightControlSoftware.uxf | 34 + ...iagramLightControlSoftwareModelPackage.pdf | Bin 0 -> 11588 bytes .../diagrams/classDiagramLightControlUnit.pdf | Bin 0 -> 11349 bytes .../diagrams/classDiagramLightControlUnit.uxf | 75 ++ .../classDiagramLightControlUnit_simple.pdf | Bin 0 -> 9924 bytes .../classDiagramLightControlUnit_simple.uxf | 39 + .../requierments/bullet points.txt | 85 +++ src/lightControlSoftware/.classpath | 14 + src/lightControlSoftware/.gitignore | 1 + .../ressourcen/images/Delete.png | Bin 0 -> 3325 bytes .../ressourcen/images/Edit.png | Bin 0 -> 3017 bytes .../ressourcen/images/Icon Test.png | Bin 0 -> 7230 bytes .../ressourcen/images/Kiwi_Logo.png | Bin 0 -> 22115 bytes .../ressourcen/lib/ImageButton.jar | Bin 0 -> 975 bytes .../ressourcen/lib/NumberField.jar | Bin 0 -> 1536 bytes .../ressourcen/lib/TreeStageSwitchButton.jar | Bin 0 -> 4010 bytes .../ressourcen/style/Style.css | 93 +++ .../src/appliaction/MainKiwi.java | 33 + .../src/model/KiwiModelHandler.java | 173 +++++ .../src/model/fixture/DmxValues.java | 69 ++ .../src/model/fixture/Fixture.java | 104 +++ .../src/model/fixture/FixtureRepository.java | 80 +++ .../src/model/generic/PropertyItem.java | 16 + .../src/model/group/Group.java | 213 ++++++ .../src/model/group/GroupReposirory.java | 45 ++ .../lightController/LightControllable.java | 16 + .../artNet/ArtNetController.java | 219 ++++++ .../artNetCommands/ActivateGroupCommand.java | 9 + .../artNetCommands/ActivatePresetCommand.java | 9 + .../artNetCommands/ClearPresetCommand.java | 9 + .../DeactivateGroupCommand.java | 9 + .../DeactivatePresetCommand.java | 9 + .../PresetActivatedCommand.java | 15 + .../PresetDeactivatedCommand.java | 15 + .../SetChannelStateCommand.java | 11 + .../artNetCommands/SetPresetCommand.java | 15 + .../artNet/artNetNode/ArtNetNode.java | 98 +++ .../artNetNode/ArtNetNodeRepository.java | 71 ++ .../artNet/artNetPakages/ArtCommand.java | 108 +++ .../artNet/artNetPakages/ArtDataReply.java | 142 ++++ .../artNet/artNetPakages/ArtDataRequest.java | 109 +++ .../artNet/artNetPakages/ArtDmx.java | 74 ++ .../artNetPakages/ArtNetCodeLibrary.java | 53 ++ .../artNet/artNetPakages/ArtPoll.java | 62 ++ .../artNet/artNetPakages/ArtPollReply.java | 543 ++++++++++++++ .../artNet/udpListener/UdpListener.java | 159 ++++ .../model/lightingScene/LightingScene.java | 12 + .../composition/Composition.java | 72 ++ .../composition/CompositionRepository.java | 33 + .../lightingMood/LightingMood.java | 60 ++ .../lightingMood/LightingMoodRepository.java | 38 + .../src/model/preset/Preset.java | 112 +++ .../src/model/preset/PresetRepository.java | 40 ++ .../src/view/KiwiViewHandler.java | 41 ++ .../view/composition/CompositionListItem.java | 225 ++++++ .../createPanel/CreateComposition.fxml | 244 +++++++ .../CreateCompositionController.java | 333 +++++++++ .../EditDeleteComposition.fxml | 257 +++++++ .../EditDeleteCompositionController.java | 394 ++++++++++ .../mainPanel/CompositionMainPanel.fxml | 43 ++ .../CompositionMainPanelController.java | 95 +++ .../src/view/elements/ImageButton.java | 27 + .../src/view/elements/NumberField.java | 57 ++ .../elements/ThreeStageButtonCallBack.java | 5 + .../view/elements/ThreeStageSwitchButton.java | 98 +++ .../view/fixture/FixtureGroupListCell.java | 22 + .../src/view/fixture/FixtureListCell.java | 22 + .../view/fixture/FixturesGroupListItem.java | 138 ++++ .../src/view/fixture/FixturesListItem.java | 122 ++++ .../fixture/createPanel/CreateFixture.fxml | 126 ++++ .../createPanel/CreateFixtureController.java | 175 +++++ .../fixture/editPanel/EditDeleteFixture.fxml | 170 +++++ .../EditDeleteFixtureController.java | 236 ++++++ .../src/view/group/GroupListCell.java | 22 + .../src/view/group/GroupListItem.java | 183 +++++ .../view/group/createPanel/CreateGroup.fxml | 96 +++ .../createPanel/CreateGroupController.java | 137 ++++ .../view/group/editPanel/EditDeleteGroup.fxml | 140 ++++ .../editPanel/EditDeleteGroupController.java | 214 ++++++ .../view/group/mainPanel/GroupMainPanel.fxml | 48 ++ .../mainPanel/GroupMainPanelController.java | 100 +++ .../mainPanel/LightControllerMainPanel.fxml | 36 + .../LightControllerMainPanelController.java | 64 ++ .../lightingMood/LightingMoodListItem.java | 224 ++++++ .../createPanel/CreateLightingMood.fxml | 194 +++++ .../CreateLightingMoodController.java | 211 ++++++ .../EditDeleteLightingMood.fxml | 227 ++++++ .../EditDeleteLightingMoodController.java | 299 ++++++++ .../mainPanel/LightingMoodMainPanel.fxml | 48 ++ .../mainPanel/LightingMoodMainPanel2.fxml | 111 +++ .../LightingMoodMainPanelController.java | 89 +++ .../src/view/mainScene/MainScene.fxml | 112 +++ .../view/mainScene/MainSceneController.java | 318 ++++++++ .../src/view/test/TestUI.java | 89 +++ .../src/viewModel/KiwiViewModelHandler.java | 43 ++ .../composition/CompositionRepositoryVM.java | 80 +++ .../viewModel/composition/CompositionVM.java | 112 +++ .../src/viewModel/fixture/DmxValuesVM.java | 89 +++ .../fixture/FixtureGroupRepositoryVM.java | 81 +++ .../src/viewModel/fixture/FixtureGroupVM.java | 104 +++ .../fixture/FixtureRepositoryVM.java | 91 +++ .../src/viewModel/fixture/FixtureVM.java | 114 +++ .../viewModel/group/GroupRepositoryVM.java | 103 +++ .../src/viewModel/group/GroupVM.java | 200 ++++++ .../lightController/ArtNetControlleVM.java | 26 + .../ArtNetNodeRepositoryVM.java | 54 ++ .../LightingMoodRepositoryVM.java | 94 +++ .../lightingMood/LightingMoodVM.java | 225 ++++++ .../ArtNetController/ArtCommands.h | 48 ++ .../ArtNetController/ArtNetController.cpp | 676 ++++++++++++++++++ .../ArtNetController/ArtNetController.h | 49 ++ .../ArtNetController/ArtNetPackageTypes.h | 148 ++++ src/lightControlUnit/DmxChannel/DmxChannel.h | 6 + .../DmxController/DmxController.cpp | 36 + .../DmxController/DmxController.h | 21 + src/lightControlUnit/Preset/Preset.cpp | 131 ++++ src/lightControlUnit/Preset/Preset.h | 33 + src/lightControlUnit/PresetLed/PresetLed.cpp | 20 + src/lightControlUnit/PresetLed/PresetLed.h | 17 + .../PresetRepository/PresetRepository.cpp | 171 +++++ .../PresetRepository/PresetRepository.h | 34 + .../PresetStoredInfromation.h | 9 + src/lightControlUnit/config/config.h | 68 ++ src/lightControlUnit/lightControlUnit.ino | 125 ++++ 126 files changed, 12022 insertions(+), 1 deletion(-) create mode 100644 doc/codeDocumentation/diagrams/classDiagramLightControlSoftware.uxf create mode 100644 doc/codeDocumentation/diagrams/classDiagramLightControlSoftwareModelPackage.pdf create mode 100644 doc/codeDocumentation/diagrams/classDiagramLightControlUnit.pdf create mode 100644 doc/codeDocumentation/diagrams/classDiagramLightControlUnit.uxf create mode 100644 doc/codeDocumentation/diagrams/classDiagramLightControlUnit_simple.pdf create mode 100644 doc/codeDocumentation/diagrams/classDiagramLightControlUnit_simple.uxf create mode 100644 doc/codeDocumentation/requierments/bullet points.txt create mode 100644 src/lightControlSoftware/.classpath create mode 100644 src/lightControlSoftware/.gitignore create mode 100644 src/lightControlSoftware/ressourcen/images/Delete.png create mode 100644 src/lightControlSoftware/ressourcen/images/Edit.png create mode 100644 src/lightControlSoftware/ressourcen/images/Icon Test.png create mode 100644 src/lightControlSoftware/ressourcen/images/Kiwi_Logo.png create mode 100644 src/lightControlSoftware/ressourcen/lib/ImageButton.jar create mode 100644 src/lightControlSoftware/ressourcen/lib/NumberField.jar create mode 100644 src/lightControlSoftware/ressourcen/lib/TreeStageSwitchButton.jar create mode 100644 src/lightControlSoftware/ressourcen/style/Style.css create mode 100644 src/lightControlSoftware/src/appliaction/MainKiwi.java create mode 100644 src/lightControlSoftware/src/model/KiwiModelHandler.java create mode 100644 src/lightControlSoftware/src/model/fixture/DmxValues.java create mode 100644 src/lightControlSoftware/src/model/fixture/Fixture.java create mode 100644 src/lightControlSoftware/src/model/fixture/FixtureRepository.java create mode 100644 src/lightControlSoftware/src/model/generic/PropertyItem.java create mode 100644 src/lightControlSoftware/src/model/group/Group.java create mode 100644 src/lightControlSoftware/src/model/group/GroupReposirory.java create mode 100644 src/lightControlSoftware/src/model/lightController/LightControllable.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/ArtNetController.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ActivateGroupCommand.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ActivatePresetCommand.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ClearPresetCommand.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/DeactivateGroupCommand.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/DeactivatePresetCommand.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/PresetActivatedCommand.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/PresetDeactivatedCommand.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/SetChannelStateCommand.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/SetPresetCommand.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetNode/ArtNetNode.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetNode/ArtNetNodeRepository.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtCommand.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDataReply.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDataRequest.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDmx.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtNetCodeLibrary.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtPoll.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtPollReply.java create mode 100644 src/lightControlSoftware/src/model/lightController/artNet/udpListener/UdpListener.java create mode 100644 src/lightControlSoftware/src/model/lightingScene/LightingScene.java create mode 100644 src/lightControlSoftware/src/model/lightingScene/composition/Composition.java create mode 100644 src/lightControlSoftware/src/model/lightingScene/composition/CompositionRepository.java create mode 100644 src/lightControlSoftware/src/model/lightingScene/lightingMood/LightingMood.java create mode 100644 src/lightControlSoftware/src/model/lightingScene/lightingMood/LightingMoodRepository.java create mode 100644 src/lightControlSoftware/src/model/preset/Preset.java create mode 100644 src/lightControlSoftware/src/model/preset/PresetRepository.java create mode 100644 src/lightControlSoftware/src/view/KiwiViewHandler.java create mode 100644 src/lightControlSoftware/src/view/composition/CompositionListItem.java create mode 100644 src/lightControlSoftware/src/view/composition/createPanel/CreateComposition.fxml create mode 100644 src/lightControlSoftware/src/view/composition/createPanel/CreateCompositionController.java create mode 100644 src/lightControlSoftware/src/view/composition/editDeletePanel/EditDeleteComposition.fxml create mode 100644 src/lightControlSoftware/src/view/composition/editDeletePanel/EditDeleteCompositionController.java create mode 100644 src/lightControlSoftware/src/view/composition/mainPanel/CompositionMainPanel.fxml create mode 100644 src/lightControlSoftware/src/view/composition/mainPanel/CompositionMainPanelController.java create mode 100644 src/lightControlSoftware/src/view/elements/ImageButton.java create mode 100644 src/lightControlSoftware/src/view/elements/NumberField.java create mode 100644 src/lightControlSoftware/src/view/elements/ThreeStageButtonCallBack.java create mode 100644 src/lightControlSoftware/src/view/elements/ThreeStageSwitchButton.java create mode 100644 src/lightControlSoftware/src/view/fixture/FixtureGroupListCell.java create mode 100644 src/lightControlSoftware/src/view/fixture/FixtureListCell.java create mode 100644 src/lightControlSoftware/src/view/fixture/FixturesGroupListItem.java create mode 100644 src/lightControlSoftware/src/view/fixture/FixturesListItem.java create mode 100644 src/lightControlSoftware/src/view/fixture/createPanel/CreateFixture.fxml create mode 100644 src/lightControlSoftware/src/view/fixture/createPanel/CreateFixtureController.java create mode 100644 src/lightControlSoftware/src/view/fixture/editPanel/EditDeleteFixture.fxml create mode 100644 src/lightControlSoftware/src/view/fixture/editPanel/EditDeleteFixtureController.java create mode 100644 src/lightControlSoftware/src/view/group/GroupListCell.java create mode 100644 src/lightControlSoftware/src/view/group/GroupListItem.java create mode 100644 src/lightControlSoftware/src/view/group/createPanel/CreateGroup.fxml create mode 100644 src/lightControlSoftware/src/view/group/createPanel/CreateGroupController.java create mode 100644 src/lightControlSoftware/src/view/group/editPanel/EditDeleteGroup.fxml create mode 100644 src/lightControlSoftware/src/view/group/editPanel/EditDeleteGroupController.java create mode 100644 src/lightControlSoftware/src/view/group/mainPanel/GroupMainPanel.fxml create mode 100644 src/lightControlSoftware/src/view/group/mainPanel/GroupMainPanelController.java create mode 100644 src/lightControlSoftware/src/view/lightController/mainPanel/LightControllerMainPanel.fxml create mode 100644 src/lightControlSoftware/src/view/lightController/mainPanel/LightControllerMainPanelController.java create mode 100644 src/lightControlSoftware/src/view/lightingMood/LightingMoodListItem.java create mode 100644 src/lightControlSoftware/src/view/lightingMood/createPanel/CreateLightingMood.fxml create mode 100644 src/lightControlSoftware/src/view/lightingMood/createPanel/CreateLightingMoodController.java create mode 100644 src/lightControlSoftware/src/view/lightingMood/editDeletePanel/EditDeleteLightingMood.fxml create mode 100644 src/lightControlSoftware/src/view/lightingMood/editDeletePanel/EditDeleteLightingMoodController.java create mode 100644 src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanel.fxml create mode 100644 src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanel2.fxml create mode 100644 src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanelController.java create mode 100644 src/lightControlSoftware/src/view/mainScene/MainScene.fxml create mode 100644 src/lightControlSoftware/src/view/mainScene/MainSceneController.java create mode 100644 src/lightControlSoftware/src/view/test/TestUI.java create mode 100644 src/lightControlSoftware/src/viewModel/KiwiViewModelHandler.java create mode 100644 src/lightControlSoftware/src/viewModel/composition/CompositionRepositoryVM.java create mode 100644 src/lightControlSoftware/src/viewModel/composition/CompositionVM.java create mode 100644 src/lightControlSoftware/src/viewModel/fixture/DmxValuesVM.java create mode 100644 src/lightControlSoftware/src/viewModel/fixture/FixtureGroupRepositoryVM.java create mode 100644 src/lightControlSoftware/src/viewModel/fixture/FixtureGroupVM.java create mode 100644 src/lightControlSoftware/src/viewModel/fixture/FixtureRepositoryVM.java create mode 100644 src/lightControlSoftware/src/viewModel/fixture/FixtureVM.java create mode 100644 src/lightControlSoftware/src/viewModel/group/GroupRepositoryVM.java create mode 100644 src/lightControlSoftware/src/viewModel/group/GroupVM.java create mode 100644 src/lightControlSoftware/src/viewModel/lightController/ArtNetControlleVM.java create mode 100644 src/lightControlSoftware/src/viewModel/lightController/ArtNetNodeRepositoryVM.java create mode 100644 src/lightControlSoftware/src/viewModel/lightingMood/LightingMoodRepositoryVM.java create mode 100644 src/lightControlSoftware/src/viewModel/lightingMood/LightingMoodVM.java create mode 100644 src/lightControlUnit/ArtNetController/ArtCommands.h create mode 100644 src/lightControlUnit/ArtNetController/ArtNetController.cpp create mode 100644 src/lightControlUnit/ArtNetController/ArtNetController.h create mode 100644 src/lightControlUnit/ArtNetController/ArtNetPackageTypes.h create mode 100644 src/lightControlUnit/DmxChannel/DmxChannel.h create mode 100644 src/lightControlUnit/DmxController/DmxController.cpp create mode 100644 src/lightControlUnit/DmxController/DmxController.h create mode 100644 src/lightControlUnit/Preset/Preset.cpp create mode 100644 src/lightControlUnit/Preset/Preset.h create mode 100644 src/lightControlUnit/PresetLed/PresetLed.cpp create mode 100644 src/lightControlUnit/PresetLed/PresetLed.h create mode 100644 src/lightControlUnit/PresetRepository/PresetRepository.cpp create mode 100644 src/lightControlUnit/PresetRepository/PresetRepository.h create mode 100644 src/lightControlUnit/PresetStoredInfromation/PresetStoredInfromation.h create mode 100644 src/lightControlUnit/config/config.h create mode 100644 src/lightControlUnit/lightControlUnit.ino diff --git a/.gitignore b/.gitignore index e69de29..688ab37 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,23 @@ +# project documentation +/doc/projectDocumentation/Medien +/doc/projectDocumentation/*.aux +/doc/projectDocumentation/*.bbl +/doc/projectDocumentation/*.bib +/doc/projectDocumentation/*.blg +/doc/projectDocumentation/*.gz +/doc/projectDocumentation/*.lof +/doc/projectDocumentation/*.log +/doc/projectDocumentation/*.lol +/doc/projectDocumentation/*.lot +/doc/projectDocumentation/*.out +/doc/projectDocumentation/*.run* +/doc/projectDocumentation/*.toc + +# libraries Arduino +/src/lightControlUnit/libraries + +# vsCode +/.vscode + +# eclips +*.project \ No newline at end of file diff --git a/README.md b/README.md index ba55221..0c5661d 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ KIWI is a light control system for DMX-fixtures in a home environment. ## Features +With this software, you can controlle DMX fixture via Art-Net ## Project status -Work in progress +Sofwtare: DONE +Documentation: WORK IN PROGRESS ## Author Sebastian Böttger diff --git a/doc/codeDocumentation/diagrams/classDiagramLightControlSoftware.uxf b/doc/codeDocumentation/diagrams/classDiagramLightControlSoftware.uxf new file mode 100644 index 0000000..6b2b1bd --- /dev/null +++ b/doc/codeDocumentation/diagrams/classDiagramLightControlSoftware.uxf @@ -0,0 +1,34 @@ +<diagram program="umletino" version="15.1"><zoom_level>13</zoom_level><element><id>UMLClass</id><coordinates><x>13</x><y>377</y><w>130</w><h>39</h></coordinates><panel_attributes>Fixture</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>0</x><y>260</y><w>169</w><h>39</h></coordinates><panel_attributes>FixturesRepository</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>247</x><y>260</y><w>169</w><h>39</h></coordinates><panel_attributes>GroupsRepository</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>260</x><y>377</y><w>130</w><h>39</h></coordinates><panel_attributes>Group</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>364</x><y>663</y><w>234</w><h>39</h></coordinates><panel_attributes>LightingMoodsRepository</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>416</x><y>793</y><w>130</w><h>39</h></coordinates><panel_attributes>LightingMood</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>65</x><y>286</y><w>39</w><h>117</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>10;70;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>312</x><y>286</y><w>39</w><h>117</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>10;70;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>468</x><y>689</y><w>39</w><h>130</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>10;80;10;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>52</x><y>663</y><w>234</w><h>39</h></coordinates><panel_attributes>CompositionsRepository</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>91</x><y>780</y><w>156</w><h>39</h></coordinates><panel_attributes>Composition</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>156</x><y>689</y><w>39</w><h>117</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>10;70;10;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>507</x><y>91</y><w>156</w><h>39</h></coordinates><panel_attributes>ArtNetController +</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>507</x><y>195</y><w>156</w><h>52</h></coordinates><panel_attributes><<interface>> +LightControllable</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>572</x><y>117</y><w>39</w><h>104</h></coordinates><panel_attributes>lt=<<.</panel_attributes><additional_attributes>10;60;10;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>247</x><y>91</y><w>169</w><h>39</h></coordinates><panel_attributes>KiwiModelHandler</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>312</x><y>117</y><w>39</w><h>169</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>10;110;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>65</x><y>91</y><w>208</w><h>195</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>10;130;10;10;140;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>247</x><y>520</y><w>169</w><h>39</h></coordinates><panel_attributes>PresetsRepository</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>273</x><y>611</y><w>104</w><h>39</h></coordinates><panel_attributes>Preset +</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>312</x><y>546</y><w>39</w><h>91</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>10;50;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>156</x><y>403</y><w>130</w><h>286</h></coordinates><panel_attributes>lt=<->>>></panel_attributes><additional_attributes>10;200;10;10;80;10</additional_attributes></element><element><id>Relation</id><coordinates><x>377</x><y>403</y><w>130</w><h>286</h></coordinates><panel_attributes>lt=<->>>></panel_attributes><additional_attributes>80;200;80;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>156</x><y>260</y><w>130</w><h>143</h></coordinates><panel_attributes>lt=<->>>></panel_attributes><additional_attributes>10;10;40;10;40;90;80;90</additional_attributes></element><element><id>Relation</id><coordinates><x>65</x><y>403</y><w>39</w><h>286</h></coordinates><panel_attributes>lt=-></panel_attributes><additional_attributes>10;200;10;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>273</x><y>910</y><w>130</w><h>39</h></coordinates><panel_attributes>DmxValues</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>390</x><y>819</y><w>117</w><h>143</h></coordinates><panel_attributes>lt=<-</panel_attributes><additional_attributes>10;90;70;90;70;10</additional_attributes></element><element><id>Relation</id><coordinates><x>13</x><y>403</y><w>286</w><h>559</h></coordinates><panel_attributes>lt=<-</panel_attributes><additional_attributes>200;410;10;410;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>195</x><y>403</y><w>117</w><h>234</h></coordinates><panel_attributes>lt=<->>>></panel_attributes><additional_attributes>70;10;70;70;10;70;10;130;70;130;70;160</additional_attributes></element><element><id>UMLClass</id><coordinates><x>507</x><y>0</y><w>156</w><h>39</h></coordinates><panel_attributes>UdpListener</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>260</x><y>728</y><w>130</w><h>52</h></coordinates><panel_attributes><<interface>> +LightingScene</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>377</x><y>728</y><w>91</w><h>91</h></coordinates><panel_attributes>lt=<<.</panel_attributes><additional_attributes>10;10;50;10;50;50</additional_attributes></element><element><id>Relation</id><coordinates><x>208</x><y>728</y><w>78</w><h>78</h></coordinates><panel_attributes>lt=<<.</panel_attributes><additional_attributes>40;10;10;10;10;40</additional_attributes></element><element><id>Relation</id><coordinates><x>312</x><y>637</y><w>39</w><h>117</h></coordinates><panel_attributes>lt=<->>>></panel_attributes><additional_attributes>10;70;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>182</x><y>806</y><w>117</w><h>130</h></coordinates><panel_attributes>lt=<-</panel_attributes><additional_attributes>70;80;10;80;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>572</x><y>26</y><w>39</w><h>91</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>10;10;10;50</additional_attributes></element><element><id>Relation</id><coordinates><x>403</x><y>91</y><w>130</w><h>39</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>80;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>377</x><y>234</y><w>234</w><h>169</h></coordinates><panel_attributes>lt=<->>>></panel_attributes><additional_attributes>160;10;160;110;10;110</additional_attributes></element><element><id>UMLClass</id><coordinates><x>637</x><y>910</y><w>130</w><h>39</h></coordinates><panel_attributes>PropertyItem</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>390</x><y>117</y><w>78</w><h>429</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>10;310;10;260;40;260;40;60;10;60;10;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>728</x><y>91</y><w>195</w><h>39</h></coordinates><panel_attributes>ArtNetNodeRepository</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>728</x><y>195</y><w>195</w><h>39</h></coordinates><panel_attributes>ArtNetNode</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>624</x><y>312</y><w>208</w><h>182</h></coordinates><panel_attributes>artNetPakages +-- ++ ArtCommand ++ ArtDataReply ++ ArtDataRequest ++ ArtDmx ++ ArtNetCodeLibrary ++ ArtPoll ++ ArtPollReply +valign=top +halign=left +</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLPackage</id><coordinates><x>624</x><y>533</y><w>234</w><h>221</h></coordinates><panel_attributes>artNetCommands +-- ++ ActivateGroupCommand ++ ActivatePresetCommand ++ ClearPresetCommand ++ DeactivateGroupCommand ++ DeactivatePresetCommand ++ PresetActivatedCommand ++ PresetDeactivatedCommand ++ SetChannelStateCommand ++ SetPresetCommand +valign=top +halign=left +</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>650</x><y>91</y><w>104</w><h>39</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>60;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>806</x><y>117</y><w>39</w><h>104</h></coordinates><panel_attributes>lt=<->>>>></panel_attributes><additional_attributes>10;60;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>637</x><y>117</y><w>260</w><h>468</h></coordinates><panel_attributes>lt=.> +m2=<<use>></panel_attributes><additional_attributes>10;10;10;20;60;20;60;110;180;110;180;310;130;310;130;340</additional_attributes></element><element><id>Relation</id><coordinates><x>611</x><y>117</y><w>260</w><h>247</h></coordinates><panel_attributes>lt=.> +m2= <<use>></panel_attributes><additional_attributes>10;10;10;30;60;30;60;140;150;140;150;170</additional_attributes></element><element><id>Relation</id><coordinates><x>689</x><y>845</y><w>39</w><h>91</h></coordinates><panel_attributes>lt=<<-</panel_attributes><additional_attributes>10;50;10;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>637</x><y>819</y><w>130</w><h>39</h></coordinates><panel_attributes>halign=center +valign=center +lt=. +Alle Klassen</panel_attributes><additional_attributes></additional_attributes></element></diagram> \ No newline at end of file diff --git a/doc/codeDocumentation/diagrams/classDiagramLightControlSoftwareModelPackage.pdf b/doc/codeDocumentation/diagrams/classDiagramLightControlSoftwareModelPackage.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c1b6235c517e0fbd03399f27c445bddbeeb4026c GIT binary patch literal 11588 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 literal 0 HcmV?d00001 diff --git a/doc/codeDocumentation/diagrams/classDiagramLightControlUnit.pdf b/doc/codeDocumentation/diagrams/classDiagramLightControlUnit.pdf new file mode 100644 index 0000000000000000000000000000000000000000..04b514ec4468d215062e7306ad4b1caff49400f3 GIT binary patch literal 11349 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 NH0DxOb@g}S0swnvzPSJZ literal 0 HcmV?d00001 diff --git a/doc/codeDocumentation/diagrams/classDiagramLightControlUnit.uxf b/doc/codeDocumentation/diagrams/classDiagramLightControlUnit.uxf new file mode 100644 index 0000000..74cac62 --- /dev/null +++ b/doc/codeDocumentation/diagrams/classDiagramLightControlUnit.uxf @@ -0,0 +1,75 @@ +<diagram program="umletino" version="15.1"><zoom_level>10</zoom_level><help_text>Space for diagram notes</help_text><element><id>UMLClass</id><coordinates><x>468</x><y>100</y><w>140</w><h>80</h></coordinates><panel_attributes><<struct>> +DmxChannel +-- +value: unsigned char +isUsed: bool +</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>158</x><y>90</y><w>220</w><h>100</h></coordinates><panel_attributes><<struct>> +PresetStoredInfromation +-- +state: unsigned char +groupId: unsigned char +dmxChannels: DmxChannel[100] +</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>368</x><y>160</y><w>120</w><h>40</h></coordinates><panel_attributes>lt=->>>>> +m1=n +m2=1</panel_attributes><additional_attributes>100;10;10;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>78</x><y>220</y><w>340</w><h>210</h></coordinates><panel_attributes>Preset +-- +- eepromAdress: int +- presetLed: PresetLed +-- ++ Preset(eepromAdress: int, LedId: int) ++ loadPresetData(): void ++ setState(state: unsigned char): void ++ setChannelValue(channel: int, value: unsigned char): void ++ setGroupId(groupId: unsigned char): void ++ getGroupId(): unsigned char ++ getDmxChannel(): DmxChannel[]</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>258</x><y>180</y><w>30</w><h>60</h></coordinates><panel_attributes>lt=<<-</panel_attributes><additional_attributes>10;10;10;40</additional_attributes></element><element><id>UMLClass</id><coordinates><x>298</x><y>470</y><w>440</w><h>200</h></coordinates><panel_attributes>PresetRepository +-- +- presets: Preset[6] +- dmxController: DmxController +-- ++ PresetRepository(eepromStartAdress: int, dmxController: DmxController) ++ activatePreset(presetId: int): void ++ deaktivatePreset(presetId: int): void ++ setPresetActive(presetId: int): void ++ setPresetDeactive(presetId: int): void ++ activateGroup(groupId: byte): void</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>838</x><y>470</y><w>350</w><h>70</h></coordinates><panel_attributes>DmxController +-- +-- ++ DmxController() ++ setChannelValue(channel: int, value: unsigned char): void</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>728</x><y>520</y><w>130</w><h>40</h></coordinates><panel_attributes>lt=<<<<- +m1=1 +m2=1</panel_attributes><additional_attributes>10;10;110;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>678</x><y>90</y><w>240</w><h>90</h></coordinates><panel_attributes>main +-- ++ presetRepository: PresetRepository ++ dmxController: DmxController ++ artNetController: ArtNetController +-- +</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>908</x><y>120</y><w>280</w><h>370</h></coordinates><panel_attributes>lt=->>>>> +m1=1 +m2=1</panel_attributes><additional_attributes>250;350;250;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>728</x><y>90</y><w>490</w><h>570</h></coordinates><panel_attributes>lt=->>>>> +m1=1 +m2=1</panel_attributes><additional_attributes>10;540;470;540;470;10;190;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>758</x><y>230</y><w>360</w><h>180</h></coordinates><panel_attributes>ArtNetController +-- +- presetRepository: PresetRepository +- dmxController: DmxController +-- ++ ArtNetController(pR: PresetRepository, dC: DmxController) ++ sendPresetActivated(presetId: int): void ++ sendPresetDeactivated(presetId: int): void</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>908</x><y>150</y><w>230</w><h>100</h></coordinates><panel_attributes>lt=->>>>> +m1=1 +m2=1</panel_attributes><additional_attributes>200;80;200;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>728</x><y>400</y><w>80</w><h>130</h></coordinates><panel_attributes>lt=<<<<- +m1=1 +m2=1</panel_attributes><additional_attributes>50;10;50;100;10;100</additional_attributes></element><element><id>Relation</id><coordinates><x>798</x><y>400</y><w>60</w><h>130</h></coordinates><panel_attributes>lt=<<<<- +m1=1 +m2=1</panel_attributes><additional_attributes>10;10;10;100;40;100</additional_attributes></element><element><id>Relation</id><coordinates><x>248</x><y>420</y><w>70</w><h>100</h></coordinates><panel_attributes>lt=->>>>> +m1=6 +m2=1</panel_attributes><additional_attributes>10;10;10;70;50;70</additional_attributes></element><element><id>UMLClass</id><coordinates><x>498</x><y>230</y><w>240</w><h>110</h></coordinates><panel_attributes>PresetLed +-- +- redPinOut: int +- greenPinOut: int +- bluePinOut: int +-- ++ PresetLed(r: int, g: int, b: int ) ++ setColor(r: bool, g: bool, b: bool): void</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>408</x><y>230</y><w>110</w><h>40</h></coordinates><panel_attributes>lt=->>>>> +m1=1 +m2=1</panel_attributes><additional_attributes>90;10;10;10</additional_attributes></element></diagram> \ No newline at end of file diff --git a/doc/codeDocumentation/diagrams/classDiagramLightControlUnit_simple.pdf b/doc/codeDocumentation/diagrams/classDiagramLightControlUnit_simple.pdf new file mode 100644 index 0000000000000000000000000000000000000000..efed75be781d4598ea6ced19afefa0327b431a11 GIT binary patch literal 9924 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 Q4NGG)OD<JaSARDy0NyskumAu6 literal 0 HcmV?d00001 diff --git a/doc/codeDocumentation/diagrams/classDiagramLightControlUnit_simple.uxf b/doc/codeDocumentation/diagrams/classDiagramLightControlUnit_simple.uxf new file mode 100644 index 0000000..42508d7 --- /dev/null +++ b/doc/codeDocumentation/diagrams/classDiagramLightControlUnit_simple.uxf @@ -0,0 +1,39 @@ +<diagram program="umletino" version="15.1"><zoom_level>12</zoom_level><element><id>UMLClass</id><coordinates><x>108</x><y>204</y><w>120</w><h>36</h></coordinates><panel_attributes>lt=.. +config</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>684</x><y>336</y><w>144</w><h>36</h></coordinates><panel_attributes>ArtNetController</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>204</x><y>612</y><w>144</w><h>36</h></coordinates><panel_attributes>lt=.. +DmxChannel +</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>180</x><y>504</y><w>192</w><h>36</h></coordinates><panel_attributes>lt=.. +PresetStoredInformation +</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>264</x><y>432</y><w>96</w><h>36</h></coordinates><panel_attributes>Preset</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>180</x><y>336</y><w>144</w><h>36</h></coordinates><panel_attributes>PresetRepository</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>84</x><y>432</y><w>96</w><h>36</h></coordinates><panel_attributes>PresetLed</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>444</x><y>336</y><w>144</w><h>36</h></coordinates><panel_attributes>DmxController +</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>444</x><y>204</y><w>144</w><h>36</h></coordinates><panel_attributes>LightControlUnit</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>684</x><y>516</y><w>144</w><h>36</h></coordinates><panel_attributes>lt=.. +ArtNetPackages +</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>72</x><y>168</y><w>792</w><h>108</h></coordinates><panel_attributes>lt=. +Hauptapplikation</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>684</x><y>444</y><w>120</w><h>36</h></coordinates><panel_attributes>lt=.. +ArtCommands +</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>216</x><y>204</y><w>252</w><h>36</h></coordinates><panel_attributes>lt=<-</panel_attributes><additional_attributes>10;10;190;10</additional_attributes></element><element><id>Relation</id><coordinates><x>312</x><y>336</y><w>156</w><h>48</h></coordinates><panel_attributes>lt=<<<<-> +m1=1 +m2=1</panel_attributes><additional_attributes>10;10;110;10</additional_attributes></element><element><id>Relation</id><coordinates><x>240</x><y>216</y><w>228</w><h>144</h></coordinates><panel_attributes>lt=<<<<<-> +m1=1 +m2=1</panel_attributes><additional_attributes>170;10;10;10;10;100</additional_attributes></element><element><id>Relation</id><coordinates><x>576</x><y>216</y><w>216</w><h>144</h></coordinates><panel_attributes>lt=<<<<<-> +m1=1 +m2=1</panel_attributes><additional_attributes>10;10;150;10;150;100</additional_attributes></element><element><id>Relation</id><coordinates><x>504</x><y>228</y><w>48</w><h>132</h></coordinates><panel_attributes>lt=<<<<<-> +m1=1 +m2=1</panel_attributes><additional_attributes>10;10;10;90</additional_attributes></element><element><id>Relation</id><coordinates><x>576</x><y>336</y><w>132</w><h>48</h></coordinates><panel_attributes>lt=<<<<-> +m1=1 +m2=1</panel_attributes><additional_attributes>90;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>804</x><y>360</y><w>36</w><h>180</h></coordinates><panel_attributes>lt=<-</panel_attributes><additional_attributes>10;130;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>732</x><y>360</y><w>36</w><h>108</h></coordinates><panel_attributes>lt=<-</panel_attributes><additional_attributes>10;70;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>312</x><y>360</y><w>420</w><h>84</h></coordinates><panel_attributes>lt=<<<<-> +m1=1 +m2=1</panel_attributes><additional_attributes>320;10;320;50;80;50;80;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>264</x><y>528</y><w>60</w><h>108</h></coordinates><panel_attributes>lt=<->>>>> +m1=100 +m2=1</panel_attributes><additional_attributes>10;70;10;10</additional_attributes></element><element><id>UMLClass</id><coordinates><x>636</x><y>288</y><w>228</w><h>396</h></coordinates><panel_attributes>lt=. +valign=bottom +halign=center +Art-Net</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>396</x><y>288</y><w>228</w><h>396</h></coordinates><panel_attributes>lt=. +valign=bottom +halign=center +DMX</panel_attributes><additional_attributes></additional_attributes></element><element><id>UMLClass</id><coordinates><x>72</x><y>288</y><w>312</w><h>396</h></coordinates><panel_attributes>lt=. +valign=bottom +halign=center +Presets</panel_attributes><additional_attributes></additional_attributes></element><element><id>Relation</id><coordinates><x>300</x><y>456</y><w>36</w><h>72</h></coordinates><panel_attributes>lt=<<-</panel_attributes><additional_attributes>10;40;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>168</x><y>444</y><w>120</w><h>48</h></coordinates><panel_attributes>lt=<<<<<-> +m1=1 +m2=1</panel_attributes><additional_attributes>80;10;10;10</additional_attributes></element><element><id>Relation</id><coordinates><x>240</x><y>360</y><w>108</w><h>96</h></coordinates><panel_attributes>lt=<<<<<-> +m1=1 +m2=6</panel_attributes><additional_attributes>10;10;10;40;60;40;60;60</additional_attributes></element></diagram> \ No newline at end of file diff --git a/doc/codeDocumentation/requierments/bullet points.txt b/doc/codeDocumentation/requierments/bullet points.txt new file mode 100644 index 0000000..6f24e92 --- /dev/null +++ b/doc/codeDocumentation/requierments/bullet points.txt @@ -0,0 +1,85 @@ +- kontinuierliches Senden der DMX-Werte +- aktivieren und deaktivieren von Presets + -> Prüfen, ob andere Gruppen Teilaktiv sind +- Wenn eine Gruppe aktiv gesetzt wird, dann müssen alle Presets der Gruppe auf Aktiv gehen + - Nur die Schnellauswahltaste, nicht den Preset selber aktivieren +- Bei jeder Änderung eines Channels muss getestet werden, ob eine Gruppe auf Teilaktiv gestellt wird +- Alle Presetdaten müssen gespeichert werden, bei jeder Änderung. + +Event: + Preset aktivieren: + -> Preset aktivieren + -> setzten der LED + -> setzten des Prest-Status + -> Preset Daten speichern + -> setzen der DMX-Werte + -> Andere Presets der Gruppe deaktivieren. + -> Presets anderer Gruppen auf State prüfen + -> setzten des Prest-Status + -> Preset Daten speichern + + Preset deaktivieren: + -> Preset deaktivieren + -> setzten der LED + -> setzten des Prest-Status + -> Preset Daten speichern + -> setzen der DMX-Werte + -> Andere Presets der Gruppe deaktivieren. + -> Presets anderer Gruppen auf State prüfen + -> setzten des Prest-Status + -> Preset Daten speichern + + Preset leeren: + -> Preset deaktivieren + -> Preset leeren + + Setzen eines Preset Wertes: + -> Presetwert im Preset setzten + -> Preset speichern + Falls Preset aktiv + -> DMX-Wert senden + -> Andere Presets prüfen, ob diese nun aktiv sind. + + Gruppe für Preset setzen + -> Preset Gruppe setzten + Falls aktiv: + -> Preset der anderen Gruppen deaktivieren + + + Gruppe aktivieren + -> Alle Presets der Gruppe auf Part activ setzten + -> Werte Speichern + + Gruppe deaktivieren + -> Alle Presets der Gruppe deaktivieren + -> Werte Speichern + + Status abfragen + -> + + + +UDP Paket Länge + ArtPoll (Empfang A; Senden J) + min 14 + max 22 + ArtPollReply (Empfang J; Senden A) + min 207 + max 236 + ArtCommand (Empfang A,J; Senden A,J) + min 16 + max 528 + ArtDataRequest (Empfang A; Senden J) + min/max 40 + ArtDataReply (Empfang J; Senden A) + min 20 + max 532 + ArtDmx (Empfang A; Senden J) + min 20 + max 530 + +Empfangen A: max 530 +Empfangen J: max 532 + +Senden A: max 532 +Senden J: max 530 \ No newline at end of file diff --git a/src/lightControlSoftware/.classpath b/src/lightControlSoftware/.classpath new file mode 100644 index 0000000..0414b14 --- /dev/null +++ b/src/lightControlSoftware/.classpath @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="ressourcen"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/javafx-jre-16"> + <attributes> + <attribute name="module" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="lib" path="C:/Program Files/Java/javafx-sdk-16/lib/gson-2.10.1.jar"/> + <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JavaFX16"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk-19"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/src/lightControlSoftware/.gitignore b/src/lightControlSoftware/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/src/lightControlSoftware/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/src/lightControlSoftware/ressourcen/images/Delete.png b/src/lightControlSoftware/ressourcen/images/Delete.png new file mode 100644 index 0000000000000000000000000000000000000000..7a46061740c00b1671f9f5097d1a11ddc356430f GIT binary patch literal 3325 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 literal 0 HcmV?d00001 diff --git a/src/lightControlSoftware/ressourcen/images/Edit.png b/src/lightControlSoftware/ressourcen/images/Edit.png new file mode 100644 index 0000000000000000000000000000000000000000..1b50153a9c18aba044bebb2401adfb0ab501a75a GIT binary patch literal 3017 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 literal 0 HcmV?d00001 diff --git a/src/lightControlSoftware/ressourcen/images/Icon Test.png b/src/lightControlSoftware/ressourcen/images/Icon Test.png new file mode 100644 index 0000000000000000000000000000000000000000..a1faca5852d81b9f013d7036d354fed94ed78c6f GIT binary patch literal 7230 zcmeAS@N?(olHy`uVBq!ia0y~yU`PRB4mJh`hJr^^Ll_uX6Ej01N+NuHtdjF{^%7I^ zlT!66atjzhz{b9!ATc>RwL~E)H9a%WR_Xoj{Yna%DYi=CroINg1<t7%nI2U|slio# zA<0$Q$(AWf_I6x06;>6w1-Ypui3%0DIeEoa6}C!XbFK1<tw6&1N(x{lCE2!05xxNm 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 HN@xNA@5jy& literal 0 HcmV?d00001 diff --git a/src/lightControlSoftware/ressourcen/images/Kiwi_Logo.png b/src/lightControlSoftware/ressourcen/images/Kiwi_Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..258fe499e53b8097473edb430baf08089ed936f9 GIT binary patch literal 22115 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Ç`P!pOxnm8_e@EP3yhOz<IGbxq!H<6bBNJnP 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 XoVn!hXWmJmvtd15{an^LB{Ts5&pZ{c literal 0 HcmV?d00001 diff --git a/src/lightControlSoftware/ressourcen/lib/ImageButton.jar b/src/lightControlSoftware/ressourcen/lib/ImageButton.jar new file mode 100644 index 0000000000000000000000000000000000000000..4149d003c86683b837ba7e8f278ee6c432498bdb GIT binary patch literal 975 zcmWIWW@Zs#;Nak3$lYHO!GHur7+4s5T|*poJ^kGDeI5Ng-CTo1^nBg^onm14?0e?4 zkGHPgMP6@Rt#fD2Zw@lJV*KFgqo+&^0p9E!o9da~Ni#4oNHQ=m1i&qDLbE`gfq|hc zGqqelH77MUHLs*t-!nHcJ=LkSq$EF2FF7Z%xVSbb+h5pG#FlxgQzQ%56muoFT=7$@ zG`L({3|4GZ>RQ>Df7)n^^6kjY)~<gd)i$Zj`oa7s@R;@H(3&M?H_z41seXQD@AKQ= z&&M<BBrErtwOvd;c;EWY^HROgCpK$$hwi%<pfw?LiNvw{yN<tm*eP5YQ*@kr2^aHy zPX->%hcA=Y{9*TcA?LYV$a#99b_w_45bp)&gm)yndMpf+y!E-2>+5|Ez6!RtFE(Ci z<#EoB^w@WG#rw}6w(fpk+?HecA!h%hHmBcb6TGgvxI8(nEAL(&DHyJ{>Fp}1!vXgu z6xW1@&6O49iqsX=%H%QZ*^)H(phBDJ)<(Sx8oDtK?oy9=g#TBkvn9W{$Qmqm>BW_I zTUt-gNPO|<WM`t${i;>1UdL|<o$(Kk-&`R3^NH(hC)<aMqJm9Y|15TLeV)s`+3Nn> zC-*y;Loa_^)LLm_655r|{r*L348PPTg-c7TJzx5AZ#P-u@9ZS^@xzS$MN>J9j{M)& zYHc-h>67A<uNy_~ZIgC0+2wafHCBY*zw7t9@(|wA`-{YXIPS2~Wj<N@bCRo;jECIb z#dbf`b|gKt^I(wVvg5m3Zu-#h$-h}g#ZE3-!aAu#J2K(h{l9^;=PnSestL}B{PI_= zp^|sm!pT=Mx^ll<e0RnrQ&sKImM1Uv&)&moHS^ew^bK~izPK9K#NKEYsoa|q$;nas zV)<l^#057M)OwD+yBTk=!@9cdj;7lQx!bmfk7%mB^?xSaS?D>td56&BSNf|LtPSbe z`K<DAUjN2~bn)ann@e@vUe39?XXnQGH&P6Jc+xzU7E2sbDvDToLe1@CPuXvw$2X3) zB+m1`VLCJWHRH82i{)qSdu<O+#AVOi|5h+DFqpG|5-}r_2m>OYASZB8K0(Sh3=BAP oO@KGT6i{YBwgHqG5T+y&&Kd#UtZX0+Obkp6lNlHo)R;j$0N<vCJOBUy literal 0 HcmV?d00001 diff --git a/src/lightControlSoftware/ressourcen/lib/NumberField.jar b/src/lightControlSoftware/ressourcen/lib/NumberField.jar new file mode 100644 index 0000000000000000000000000000000000000000..cc1ea19b2c35c6ef752a3f36173ed5ebc89a61aa GIT binary patch literal 1536 zcmWIWW@Zs#;Nak35ZY55!GHur7+4s5T|*poJ^kGDeI5Ng-CTo1^nBg^onm14?0e?4 zkGHPgMP6@Rt#fD2Zw@lJV*KFgqo+&^0p9E!o9da~Ni#4oNHQ=m1i&r$vK!R`c?JfC zvdq+S{nVV)+|<01Vtv2T+@#bZx6IU>6usn}#Ny()p|Rn@?h<uvEjb*kTS6{`cu!^W zT_mwlH<Ve#W9b6HjEx)XwoQKH5H{^@YI)Oz*Z;WI-_p+8^HJ^FjpAR-RaN)*U(&u^ zZpN*_!gqYm<he7yKdzi#`Rw(2^Y#B(3YhJ)Gm<^>XFQ6Gi%9alaq0Z|>({jo%{FXJ z&FC=tV*6COrqcL*c&qNG(BsqQB|m-N<tur_;K(tb+f#&jCi-kUBcN;5=AqEUC;PTT z^}vOd&h9-6qgnNK9@^x!OrP^y@(Zn*+cK{0Ijub{!DH$it?j4o7+<w8aZ5e>Me^J< zKGo=esX<nD2Q=AYh4#CiFKkucE@3n?M{|ywYr*aFyBBKjSFybuce7JxiO2Lf+2w_8 zj;pWj*sGM$HhHG<sdvuz9)!g{>2`c|PN(x$yT$T{I)@JwX1w^pQL7?t-ebG4&FHws z60rrzt|!zECU`!ZqW9Oz?jXb2&YG;35`L|dp6D`hycRyMm|{}n<-PU!?9EFf1n2eX z6!8>JuimF1?d<<d!KtjzNoQY};g18NZMStj^+ReN=B{YfU0wK4!H4@nk<9c=r~aN8 zrtpH$!yL{<^)vd;f3Wl|?meC5`=h;}M&P)H^Y6pz{XNGEW0D)1_AFf7VA$589@8Cu zbV{mWO!mb4OdF?8;SsjlcSz;p$MTST&*f_pXFdE<cf9287M_Qy2CaNwcdkrkyMLkb z@jC6CThk7&J-|1^X+~4ijLU`G+c}%IZ8LYRSu}HJPGIG`7c-7C$;vdF?G!nBp}y~r zyHefi$JTSyUA%8-Z(8P4URtyMTU3nI*@{V5ZpIc`F&)1tH051{+l6rRLyeV_?u(sw zs&BcTx$<(m(9-C%n_p^nzL>FXPT?yrn{N&O)c$53{<g3Ban4Tb?dQHG2gvTOm~fY6 zs?A1~%F?fV<<4u4ObY(KD(P5A^OI=v6RA`EmSrBFxbMr3YWEYfl7Dbs`4GZf_~yQL z!Y%1@t4>UB5_W5>nHj$H@x`edxo)kSn_aEew*US;g~T-BnrXM&RR1(idc5Y^-NpP5 ztUJBtd@nZhmYX}<_JwZI=_sS68YM?Je3BDC<@9vric@0Kax6Kc=Uyt66)#-r-8}n< zsug3g+1(|s-M{yJFwHUGk@I%po&7MmvO;6-<TL}lfQKAc^u27W)L*W?xWYmAuKSA< zd!1~qJ3Lb5y;ab(PrbRtLbc?AxnuH$%~C9jxjnOXUp1Cy>J6OsX|BqIcXzVCFz_z< zenIF;Q<eIQ#2e05l1hd(J)adXU7av#QSrlao_#ltcCdb1xc$@Pvwu=%Pueo`yjthR zLy_`+yyg??`6f#H)lU4isqr;$&;Q1izt;!s5%hHztz(YA-`%s#-7WdHgpYf567Lx` z{l8uJ=6!qqqN8tH{^HG<dUt=T9;-@vnZew>|L^zytm)5+LlSFduUPdhb-_9%E9Xlq zPHHZ!>PV39HTDoZ87!fA+M>$Pcl|%#IR|wvMX3Bvu4-8=5~-{i=E)=e_n=Gq?8%(1 zL3@qfu}+RZ&c8$c#I8Q}E8PL#bZw3(ClyZ2vN<Lk_44$I$L@bz9(|0xDdN^zbu~dS zIp1JWV%NsFwQu9+^a)K6%+T8)vuw@8vyJTXMiJg^R`U|MU;bwVm3lY*>b+O8FfiQV z1eJP>Od<@3f)%+$1Qo1E<tzgO&SEye8(|8l^hCA+RC*#zNhDmB26(fwfiy5NFfmMK KU|<Mm1@QpKdYm5s literal 0 HcmV?d00001 diff --git a/src/lightControlSoftware/ressourcen/lib/TreeStageSwitchButton.jar b/src/lightControlSoftware/ressourcen/lib/TreeStageSwitchButton.jar new file mode 100644 index 0000000000000000000000000000000000000000..8bb9d01cb29dcfab2c7d9ff49d9fec20de83002f GIT binary patch literal 4010 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 literal 0 HcmV?d00001 diff --git a/src/lightControlSoftware/ressourcen/style/Style.css b/src/lightControlSoftware/ressourcen/style/Style.css new file mode 100644 index 0000000..cba1565 --- /dev/null +++ b/src/lightControlSoftware/ressourcen/style/Style.css @@ -0,0 +1,93 @@ +.root { + -fx-background-color: white; +} + +BorderPane { + -fx-background-color: white; +} + +ScrollPane { + -fx-background-color: white; +} + +SplitPane { + -fx-background-color: white; +} + +FlowPane { + -fx-background-color: white; +} + +Button { + -fx-background-radius: 25; +} + +.list-cell { + -fx-background-color: transparent; +} + +.white-background { + -fx-background-color: white; +} + +.section { + -fx-border-color: gray; + -fx-border-width: 2px; + -fx-border-radius: 5px; + -fx-padding: 10 5 5 5; +} + +.section-headline { + -fx-background-color: white; + -fx-padding: 0 5 0 5; +} + +.list-item { + -fx-border-color: black; + -fx-border-width: 1px; + -fx-border-radius: 5px; + -fx-background-color: lightgray; + -fx-background-radius: 5px; +} + +.item-diselected { + -fx-border-color: black; + -fx-border-width: 1px; + -fx-border-radius: 5px; +} + +.item-selected { + -fx-border-color: orange; + -fx-border-width: 2px; + -fx-border-radius: 5px; +} + +.lable-box { + -fx-border-color: black; + -fx-border-width: 1px; + -fx-border-radius: 5px; +} + +.footer { + -fx-background-color: lightgray +} + +.text-color-black { + -fx-text-fill: black +} + +.combo-box .list-cell +{ + -fx-background: white; + -fx-text-fill: black; +} + +.combo-box .list-view .list-cell:focused { + -fx-background-color : orange; + -fx-text-fill: white; +} + +.combo-box .list-view .list-cell:hover { + -fx-background-color : orange; + -fx-text-fill: white; +} \ No newline at end of file diff --git a/src/lightControlSoftware/src/appliaction/MainKiwi.java b/src/lightControlSoftware/src/appliaction/MainKiwi.java new file mode 100644 index 0000000..7af165c --- /dev/null +++ b/src/lightControlSoftware/src/appliaction/MainKiwi.java @@ -0,0 +1,33 @@ +package appliaction; + +import javafx.application.Application; +import javafx.stage.Stage; +import model.KiwiModelHandler; +import view.KiwiViewHandler; +import viewModel.KiwiViewModelHandler; + +public class MainKiwi extends Application{ + + private KiwiModelHandler modelHandler; + private KiwiViewModelHandler viewModelHandler; + private KiwiViewHandler viewHandler; + + @Override + public void start(Stage primaryStage) throws Exception { + this.modelHandler = new KiwiModelHandler(); + this.modelHandler.testModels(); + this.viewModelHandler = new KiwiViewModelHandler(modelHandler); + this.viewHandler = new KiwiViewHandler(primaryStage, viewModelHandler); + + viewHandler.start(); + } + + public static void main(String[] args) { + launch(args); + } + + @Override + public void stop(){ + System.exit(0); + } +} diff --git a/src/lightControlSoftware/src/model/KiwiModelHandler.java b/src/lightControlSoftware/src/model/KiwiModelHandler.java new file mode 100644 index 0000000..4651e21 --- /dev/null +++ b/src/lightControlSoftware/src/model/KiwiModelHandler.java @@ -0,0 +1,173 @@ +package model; + +import model.fixture.DmxValues; +import model.fixture.Fixture; +import model.fixture.FixtureRepository; +import model.group.Group; +import model.group.GroupReposirory; +import model.lightController.LightControllable; +import model.lightController.artNet.ArtNetController; +import model.lightingScene.composition.Composition; +import model.lightingScene.lightingMood.LightingMood; +import model.preset.PresetRepository; + +public class KiwiModelHandler { + private static FixtureRepository fixtureRepository; + private static GroupReposirory groupReposirory; + private static LightControllable lightController; + private static PresetRepository presetRepository; + + + public static FixtureRepository getFixtureRepository() { + return KiwiModelHandler.fixtureRepository; + } + + public static GroupReposirory getGroupReposirory() { + return KiwiModelHandler.groupReposirory; + } + + public static LightControllable getLightController() { + return KiwiModelHandler.lightController; + } + + public static PresetRepository getPresetRepository() { + return KiwiModelHandler.presetRepository; + } + + public static ArtNetController getArtNetController() { + return (ArtNetController) KiwiModelHandler.lightController; + } + + public KiwiModelHandler() { + KiwiModelHandler.fixtureRepository = new FixtureRepository(); + KiwiModelHandler.groupReposirory = new GroupReposirory(); + KiwiModelHandler.presetRepository = new PresetRepository(); + KiwiModelHandler.lightController = new ArtNetController(KiwiModelHandler.presetRepository); + KiwiModelHandler.presetRepository.loadPrests(); + } + + private void tempSetup() { + // Lampen hinzufügen + fixtureRepository.addFixture(new Fixture("Wohnzimmer Fernseher", 1)); + fixtureRepository.addFixture(new Fixture("Wohnzimmer Decke", 6)); + fixtureRepository.addFixture(new Fixture("Küche", 11)); + fixtureRepository.addFixture(new Fixture("Schlafzimmer", 16)); + + // Gruppe Wohnzimmer erstellen + Group wohnzimmer = new Group("Wohnzimmer", lightController); + wohnzimmer.addFixture(fixtureRepository.getFixture("Wohnzimmer Fernseher")); + wohnzimmer.addFixture(fixtureRepository.getFixture("Wohnzimmer Decke")); + insertLightingMood(wohnzimmer); + + // Gruppe Küche erstellen + Group kueche = new Group("Küche", lightController); + kueche.addFixture(fixtureRepository.getFixture("Küche")); + insertLightingMood(kueche); + + // Gruppe Schlafzimmer erstellen + Group schlafzimmer = new Group("Schlafzimmer", lightController); + schlafzimmer.addFixture(fixtureRepository.getFixture("Schlafzimmer")); + insertLightingMood(schlafzimmer); + + // Gruppe Etage erstellen + Group etage = new Group("Etage", lightController); + etage.addFixture(fixtureRepository.getFixture("Wohnzimmer Fernseher")); + etage.addFixture(fixtureRepository.getFixture("Wohnzimmer Decke")); + etage.addFixture(fixtureRepository.getFixture("Küche")); + etage.addFixture(fixtureRepository.getFixture("Schlafzimmer")); + insertLightingMood(etage); + + // Gruppen dem Repro hinzufügen + groupReposirory.addGroup(wohnzimmer); + groupReposirory.addGroup(kueche); + groupReposirory.addGroup(schlafzimmer); + groupReposirory.addGroup(etage); + + + // Preset setzten + presetRepository.getPreset(0).setGroup(wohnzimmer); + presetRepository.getPreset(0).setLightingScene(wohnzimmer.getLightingMoodRepository().getLightingMood("Rot")); + + presetRepository.getPreset(1).setGroup(wohnzimmer); + presetRepository.getPreset(1).setLightingScene(wohnzimmer.getLightingMoodRepository().getLightingMood("Grün")); + + + Composition filmeAbend = new Composition("Filmeabend"); + filmeAbend.setDmxValuesForFixture("Wohnzimmer Fernseher", new DmxValues((byte)255, (byte)0, (byte)0, (byte)0, (byte)255)); + filmeAbend.setDmxValuesForFixture("Wohnzimmer Decke", new DmxValues((byte)100, (byte)0, (byte)255, (byte)255, (byte)255)); + wohnzimmer.addComposition(filmeAbend); + + // Presets setzen +// presetRepository.getPreset(0).setGroup(wohnzimmer); +// presetRepository.getPreset(0).setLightingScene(blue); +// presetRepository.getPreset(0).updatePreset(); + +// presetRepository.getPreset(5).setGroup(etage); +// presetRepository.getPreset(5).setLightingScene(white); +// presetRepository.getPreset(5).updatePreset(); + + + +// lightController.clearPreset(0); +// lightController.setPreset(0, (byte)1, 1, (byte)0); +// lightController.setPreset(0, (byte)1, 2, (byte)0); +// lightController.setPreset(0, (byte)1, 3, (byte)255); +// lightController.setPreset(0, (byte)1, 4, (byte)255); +// lightController.setPreset(0, (byte)1, 5, (byte)0); +// lightController.setPreset(0, (byte)1, 6, (byte)0); +// lightController.setPreset(0, (byte)1, 7, (byte)0); +// lightController.setPreset(0, (byte)1, 8, (byte)255); +// lightController.setPreset(0, (byte)1, 9, (byte)255); +// lightController.setPreset(0, (byte)1, 10, (byte)0); + } + + private void insertLightingMood(Group group) { + LightingMood red = new LightingMood("Rot"); + LightingMood green = new LightingMood("Grün"); + LightingMood blue = new LightingMood("Blau"); + LightingMood white = new LightingMood("Weiß"); + + red.setDmxValues(new DmxValues((byte)255, (byte)0, (byte)255, (byte)0, (byte)0)); + green.setDmxValues(new DmxValues((byte)255, (byte)0, (byte)0, (byte)255, (byte)0)); + blue.setDmxValues(new DmxValues((byte)255, (byte)0, (byte)0, (byte)0, (byte)255)); + white.setDmxValues(new DmxValues((byte)100, (byte)0, (byte)255, (byte)255, (byte)255)); + + group.addLightingMood(red); + group.addLightingMood(green); + group.addLightingMood(blue); + group.addLightingMood(white); + } + + private void tempAction() { + Group wohnzimmer = KiwiModelHandler.groupReposirory.getGroup("Wohnzimmer"); + Group kueche = KiwiModelHandler.groupReposirory.getGroup("Küche"); + Group schlafzimmer = KiwiModelHandler.groupReposirory.getGroup("Schlafzimmer"); + Group etage = KiwiModelHandler.groupReposirory.getGroup("Etage"); + + wohnzimmer.activateLightingMood(wohnzimmer.getLightingMoodRepository().getLightingMood("Grün")); + wohnzimmer.activate(); + + kueche.activateLightingMood(kueche.getLightingMoodRepository().getLightingMood("Rot")); + kueche.activate(); + + schlafzimmer.activateLightingMood(schlafzimmer.getLightingMoodRepository().getLightingMood("Blau")); + schlafzimmer.activate(); + +// etage.activateLightingMood(etage.getLightingMoodRepository().getLightingMood("Rot")); +// etage.activate(); + } + + public void testModels() { + this.tempSetup(); + // Nodes verbinden + //this.lightController.connectToNodes(); + //this.tempAction(); + //this.lightController.connectToNodes(); + } + + public void clearRepros() { + KiwiModelHandler.fixtureRepository.clearRepro(); + KiwiModelHandler.groupReposirory.clearRepro(); + + } +} \ No newline at end of file diff --git a/src/lightControlSoftware/src/model/fixture/DmxValues.java b/src/lightControlSoftware/src/model/fixture/DmxValues.java new file mode 100644 index 0000000..2ed01ba --- /dev/null +++ b/src/lightControlSoftware/src/model/fixture/DmxValues.java @@ -0,0 +1,69 @@ +package model.fixture; + +import java.io.Serializable; + +import model.generic.PropertyItem; + +public class DmxValues extends PropertyItem implements Serializable{ + protected byte intensityMain = 0; + protected byte stropeRate = 0; + protected byte intensityRed = 0; + protected byte intensityGreen = 0; + protected byte intensityBlue = 0; + + public DmxValues() {} + + public DmxValues(byte intensityMain, byte stropeRate, byte intensityRed, byte intensityGreen, byte intensityBlue) { + super(); + this.intensityMain = intensityMain; + this.stropeRate = stropeRate; + this.intensityRed = intensityRed; + this.intensityGreen = intensityGreen; + this.intensityBlue = intensityBlue; + } + + public byte getIntensityMain() { + return intensityMain; + } + + public byte getStropeRate() { + return stropeRate; + } + + public byte getIntensityRed() { + return intensityRed; + } + + public byte getIntensityGreen() { + return intensityGreen; + } + + public byte getIntensityBlue() { + return intensityBlue; + } + + public void setIntensityMain(byte intensityMain) { + this.propertySupport.firePropertyChange("intensityMain", Byte.toUnsignedInt(this.intensityMain), Byte.toUnsignedInt(intensityMain)); + this.intensityMain = intensityMain; + } + + public void setStropeRate(byte stropeRate) { + this.propertySupport.firePropertyChange("stropeRate", Byte.toUnsignedInt(this.stropeRate), Byte.toUnsignedInt(stropeRate)); + this.stropeRate = stropeRate; + } + + public void setIntensityRed(byte intensityRed) { + this.propertySupport.firePropertyChange("intensityRed", Byte.toUnsignedInt(this.intensityRed), Byte.toUnsignedInt(intensityRed)); + this.intensityRed = intensityRed; + } + + public void setIntensityGreen(byte intensityGreen) { + this.propertySupport.firePropertyChange("intensityGreen", Byte.toUnsignedInt(this.intensityGreen), Byte.toUnsignedInt(intensityGreen)); + this.intensityGreen = intensityGreen; + } + + public void setIntensityBlue(byte intensityBlue) { + this.propertySupport.firePropertyChange("intensityBlue", Byte.toUnsignedInt(this.intensityBlue), Byte.toUnsignedInt(intensityBlue)); + this.intensityBlue = intensityBlue; + } +} diff --git a/src/lightControlSoftware/src/model/fixture/Fixture.java b/src/lightControlSoftware/src/model/fixture/Fixture.java new file mode 100644 index 0000000..c9a2842 --- /dev/null +++ b/src/lightControlSoftware/src/model/fixture/Fixture.java @@ -0,0 +1,104 @@ +package model.fixture; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import model.KiwiModelHandler; +import model.generic.PropertyItem; +import model.lightController.LightControllable; + +public class Fixture extends PropertyItem{ + private String name; + private int address; + private int numberOfChannel = 5; + private boolean isActive; + private DmxValues dmxValues; + private LightControllable lightController; + private PropertyChangeListener dmxValueListener; + + public Fixture(String name, int adress) { + super(); + this.name = name; + this.address = adress; + this.dmxValues = new DmxValues(); + this.lightController = KiwiModelHandler.getLightController(); + this.dmxValueListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + sendDmxValue(); + } + }; + this.dmxValues.addPropertyChangeListener(this.dmxValueListener); + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.propertySupport.firePropertyChange("name", this.name, name); + this.name = name; + } + + public int getAddress() { + return this.address; + } + + public void setAddress(int address) { + this.propertySupport.firePropertyChange("address", this.address, address); + this.address = address; + } + + public boolean isActive() { + return this.isActive; + } + + public DmxValues getDmxValues() { + return this.dmxValues; + } + + public void setDmxValues(DmxValues dmxValues) { + this.dmxValues.removePropertyChangeListener(dmxValueListener); + this.dmxValues = dmxValues; + this.dmxValues.addPropertyChangeListener(dmxValueListener); + if(this.isActive) { + this.sendDmxValue(); + } + } + + public void activate() { + if(!this.isActive) { + this.isActive = true; + this.propertySupport.firePropertyChange("isActive", false, true); + this.sendDmxValue(); + } + + } + + public void deactivate() { + if(this.isActive) { + this.isActive = false; + this.propertySupport.firePropertyChange("isActive", true, false); + this.sendDmxValue(); + } + } + + private void sendDmxValue(){ + if(isActive) { + this.lightController.setDmxValue(this.address, this.dmxValues.getIntensityRed()); + this.lightController.setDmxValue(this.address + 1, this.dmxValues.getIntensityGreen()); + this.lightController.setDmxValue(this.address + 2, this.dmxValues.getIntensityBlue()); + this.lightController.setDmxValue(this.address + 3, this.dmxValues.getIntensityMain()); + this.lightController.setDmxValue(this.address + 4, this.dmxValues.getStropeRate()); + + for(int i = 0; i < this.numberOfChannel; i++) { + this.lightController.activateDmxChannel(this.address + i); + } + } else { + for(int i = 0; i < this.numberOfChannel; i++) { + this.lightController.sendDmxValue(this.address + i, (byte)0x00); + this.lightController.deactivateDmxChannel(this.address + i); + } + } + } +} diff --git a/src/lightControlSoftware/src/model/fixture/FixtureRepository.java b/src/lightControlSoftware/src/model/fixture/FixtureRepository.java new file mode 100644 index 0000000..c70513f --- /dev/null +++ b/src/lightControlSoftware/src/model/fixture/FixtureRepository.java @@ -0,0 +1,80 @@ +package model.fixture; + +import java.util.Map; + +import model.generic.PropertyItem; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashMap; + +public class FixtureRepository extends PropertyItem{ + private Map<String, Fixture> fixtures; + private int activeFixtures; + private PropertyChangeListener fixtureListener; + + public FixtureRepository() { + this.fixtures = new HashMap<>(); + this.activeFixtures = 0; + this.fixtureListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("isActive")) { + if((boolean) event.getNewValue()) { + addNumberOfActiveFixtures(); + } else { + subNumberOfActiveFixtures(); + } + } + } + }; + } + + public void addFixture(Fixture fixture) { + if(this.fixtures.containsKey(fixture.getName())) { + throw new IllegalArgumentException("Es existiert bereits eine Lampe mit diesem Namen"); + } + + this.fixtures.put(fixture.getName(), fixture); + this.propertySupport.firePropertyChange("fixtures", null, fixture); + + fixture.addPropertyChangeListener(this.fixtureListener); + } + + private void addNumberOfActiveFixtures() { + this.propertySupport.firePropertyChange("activeFixtures", this.activeFixtures, this.activeFixtures+1); + this.activeFixtures++; + } + + private void subNumberOfActiveFixtures() { + this.propertySupport.firePropertyChange("activeFixtures", this.activeFixtures, this.activeFixtures-1); + this.activeFixtures--; + } + + public int getActiveFixturesCount() { + return this.activeFixtures; + } + + public Fixture getFixture(String name) { + return this.fixtures.get(name); + } + + public void removeFixture(String name) { + this.fixtures.get(name).deactivate(); + this.fixtures.get(name).removePropertyChangeListener(fixtureListener); + this.propertySupport.firePropertyChange("fixtures", this.fixtures.get(name), null); + this.fixtures.remove(name); + } + + public Map<String, Fixture> getFixtureMap() { + return this.fixtures; + } + + + + public void clearRepro() { + Map.copyOf(this.fixtures).forEach((String name, Fixture fixture) -> { + this.removeFixture(name); + }); + this.activeFixtures = 0; + } +} diff --git a/src/lightControlSoftware/src/model/generic/PropertyItem.java b/src/lightControlSoftware/src/model/generic/PropertyItem.java new file mode 100644 index 0000000..28a60b7 --- /dev/null +++ b/src/lightControlSoftware/src/model/generic/PropertyItem.java @@ -0,0 +1,16 @@ +package model.generic; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +public abstract class PropertyItem { + protected final PropertyChangeSupport propertySupport = new PropertyChangeSupport(this); + + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertySupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + propertySupport.removePropertyChangeListener(listener); + } +} diff --git a/src/lightControlSoftware/src/model/group/Group.java b/src/lightControlSoftware/src/model/group/Group.java new file mode 100644 index 0000000..a1b2dc5 --- /dev/null +++ b/src/lightControlSoftware/src/model/group/Group.java @@ -0,0 +1,213 @@ +package model.group; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import model.fixture.DmxValues; +import model.fixture.Fixture; +import model.fixture.FixtureRepository; +import model.generic.PropertyItem; +import model.lightController.LightControllable; +import model.lightingScene.LightingScene; +import model.lightingScene.LightingScene.LightingSceneType; +import model.lightingScene.composition.Composition; +import model.lightingScene.composition.CompositionRepository; +import model.lightingScene.lightingMood.LightingMood; +import model.lightingScene.lightingMood.LightingMoodRepository; + +public class Group extends PropertyItem{ + private byte id; + private String name; + private State currentState = State.DEAKTIV; + private LightingScene currentLightingScene; + private LightControllable lightController; + private FixtureRepository fixtureRepository; + private LightingMoodRepository lightingMoodRepository; + private CompositionRepository compositionRepository; + private PropertyChangeListener fixtureRepositoryListener; + + public static enum State { + AKTIV, + PARTAKTIV, + DEAKTIV + } + + + public Group(String name, LightControllable lightController) { + super(); + this.name = name; + this.lightController = lightController; + this.currentLightingScene = null; + this.fixtureRepository = new FixtureRepository(); + this.lightingMoodRepository = new LightingMoodRepository(); + this.compositionRepository = new CompositionRepository(); + this.fixtureRepositoryListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("activeFixtures")) { + int activeFixtures= (int)event.getNewValue(); + if(activeFixtures == 0) { + setCurrentState(State.DEAKTIV); + } else if(activeFixtures < fixtureRepository.getFixtureMap().size()) { + setCurrentState(State.PARTAKTIV); + } else if(activeFixtures == fixtureRepository.getFixtureMap().size()) { + setCurrentState(State.AKTIV); + } + } + } + }; + } + + public byte getId() { + return this.id; + } + + public void setId(byte id) { + this.propertySupport.firePropertyChange("id", this.id, id); + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.propertySupport.firePropertyChange("name", this.name, name); + this.name = name; + } + + public State getCurrentState() { + return this.currentState; + } + + public void setCurrentState(State currentState) { + this.propertySupport.firePropertyChange("currentState", this.currentState, currentState); + this.currentState = currentState; + } + + public void addFixture(Fixture fixture) { + this.fixtureRepository.addFixture(fixture); + this.compositionRepository.getCompositionMap().forEach((String name, Composition composition) -> { + composition.setDmxValuesForFixture(fixture.getName(), new DmxValues()); + }); + + if(this.currentLightingScene != null) { + if(this.currentLightingScene.getLightingSceneType() == LightingSceneType.LIGHTING_MOOD) { + fixture.setDmxValues(((LightingMood) this.currentLightingScene).getDmxValues()); + } else if(this.currentLightingScene.getLightingSceneType() == LightingSceneType.COMPOSITION) { + fixture.setDmxValues(((Composition) this.currentLightingScene).getDmxValuesForFixture(fixture.getName())); + } + } + this.fixtureRepository.addPropertyChangeListener(this.fixtureRepositoryListener); + if(this.currentState == State.AKTIV) { + fixture.activate(); + } + } + + public void addLightingMood(LightingMood lightingMood) { + this.lightingMoodRepository.addLightingMood(lightingMood); + } + + public void addComposition(Composition composition) { + this.compositionRepository.addComposition(composition); + } + + public void removeFixture(Fixture fixture) { + fixture.removePropertyChangeListener(this.fixtureRepositoryListener); + this.fixtureRepository.removeFixture(fixture.getName()); + this.compositionRepository.getCompositionMap().forEach((String name, Composition composition) -> { + composition.removeFixtureFromComposition(fixture.getName()); + }); + fixture.deactivate(); + } + + public void removeLightingMood(LightingMood lightingMood) { + this.lightingMoodRepository.removeLightingMood(lightingMood.getName()); + } + + public void removeComposition(Composition composition) { + this.compositionRepository.removeComposition(composition.getName()); + } + + public void activateLightingMood(LightingMood lightingMood) { + if(lightingMood != null) { + if(this.currentLightingScene != null) { + this.currentLightingScene.deactivate(); + } + this.currentLightingScene = lightingMood; + if(!lightingMood.isActive()) { + lightingMood.activate(); + } + + + this.fixtureRepository.getFixtureMap().forEach((String name, Fixture fixture) -> { + fixture.setDmxValues(lightingMood.getDmxValues()); + }); + } + if(this.currentState != State.DEAKTIV) { + this.lightController.sendDmxValue(); + } + } + + public void deactivateLightingMood(LightingMood lightingMood) { + lightingMood.deactivate(); + this.fixtureRepository.getFixtureMap().forEach((String name, Fixture fixture) -> { + fixture.setDmxValues(new DmxValues()); + }); + this.lightController.sendDmxValue(); + } + + public void activateComposition(Composition composition) { + if(this.currentLightingScene != null) { + this.currentLightingScene.deactivate(); + } + + this.currentLightingScene = composition; + if(!composition.isActive()) { + composition.activate(); + } + + + this.fixtureRepository.getFixtureMap().forEach((String name, Fixture fixture) -> { + fixture.setDmxValues(composition.getDmxValuesForFixture(name)); + }); + if(this.currentState != State.DEAKTIV) { + this.lightController.sendDmxValue(); + } + } + + public void deactivateComposition(Composition composition) { + composition.deactivate(); + this.fixtureRepository.getFixtureMap().forEach((String name, Fixture fixture) -> { + fixture.setDmxValues(new DmxValues()); + }); + this.lightController.sendDmxValue(); + } + + public FixtureRepository getFixtureRepository() { + return this.fixtureRepository; + } + + public LightingMoodRepository getLightingMoodRepository() { + return this.lightingMoodRepository; + } + + public CompositionRepository getCompositionRepository() { + return this.compositionRepository; + } + + public void activate() { + this.fixtureRepository.getFixtureMap().forEach((String name, Fixture fixture) -> { + fixture.activate(); + }); + this.lightController.sendDmxValue(); + this.lightController.activateGroup(this.id); + } + + public void deactivate() { + this.fixtureRepository.getFixtureMap().forEach((String name, Fixture fixture) -> { + fixture.deactivate(); + }); + this.lightController.sendDmxValue(); + this.lightController.deactivateGroup(this.id); + } +} \ No newline at end of file diff --git a/src/lightControlSoftware/src/model/group/GroupReposirory.java b/src/lightControlSoftware/src/model/group/GroupReposirory.java new file mode 100644 index 0000000..1643aa6 --- /dev/null +++ b/src/lightControlSoftware/src/model/group/GroupReposirory.java @@ -0,0 +1,45 @@ +package model.group; + +import java.util.Map; +import model.generic.PropertyItem; +import java.util.HashMap; + +public class GroupReposirory extends PropertyItem{ + private Map<String, Group> groups; + private byte currentGroupId = 1; + + public GroupReposirory() { + this.groups = new HashMap<>(); + } + + public void addGroup(Group group) { + if(this.groups.containsKey(group.getName())) { + throw new IllegalArgumentException("Es existiert bereits eine Gruppe mit diesem Namen"); + } + group.setId(currentGroupId); + currentGroupId++; + this.groups.put(group.getName(), group); + this.propertySupport.firePropertyChange("groups", null, group); + } + + public Group getGroup(String name) { + return groups.get(name); + } + + public void removeGroup(String name) { + this.groups.get(name).deactivate(); + this.propertySupport.firePropertyChange("groups", this.groups.get(name), null); + this.groups.remove(name); + } + + public Map<String, Group> getGroupsMap() { + return this.groups; + } + + public void clearRepro() { + Map.copyOf(this.groups).forEach((String name, Group group) -> { + this.removeGroup(name); + }); + this.currentGroupId = 0; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/LightControllable.java b/src/lightControlSoftware/src/model/lightController/LightControllable.java new file mode 100644 index 0000000..52c74e4 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/LightControllable.java @@ -0,0 +1,16 @@ +package model.lightController; + +public interface LightControllable { + public void setDmxValue(int channel, byte value); + public void sendDmxValue(); + public void sendDmxValue(int channel, byte value); + public void activateDmxChannel(int channel); + public void deactivateDmxChannel(int channel); + public void activateGroup(byte groupId); + public void deactivateGroup(byte groupId); + public void activatePreset(int presetId); + public void deactivatePreset(int presetId); + public void clearPreset(int presetId); + public void setPreset(int presetId, byte groupId, int channel, byte value); + public void connectToNodes(); +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/ArtNetController.java b/src/lightControlSoftware/src/model/lightController/artNet/ArtNetController.java new file mode 100644 index 0000000..93a496e --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/ArtNetController.java @@ -0,0 +1,219 @@ +package model.lightController.artNet; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; +import java.util.concurrent.atomic.AtomicBoolean; +import com.google.gson.Gson; +import model.lightController.LightControllable; +import model.lightController.artNet.artNetCommands.ActivateGroupCommand; +import model.lightController.artNet.artNetCommands.ActivatePresetCommand; +import model.lightController.artNet.artNetCommands.ClearPresetCommand; +import model.lightController.artNet.artNetCommands.DeactivateGroupCommand; +import model.lightController.artNet.artNetCommands.DeactivatePresetCommand; +import model.lightController.artNet.artNetCommands.SetChannelStateCommand; +import model.lightController.artNet.artNetCommands.SetPresetCommand; +import model.lightController.artNet.artNetNode.ArtNetNode; +import model.lightController.artNet.artNetNode.ArtNetNodeRepository; +import model.lightController.artNet.artNetPakages.ArtCommand; +import model.lightController.artNet.artNetPakages.ArtDataRequest; +import model.lightController.artNet.artNetPakages.ArtDmx; +import model.lightController.artNet.artNetPakages.ArtNetCodeLibrary; +import model.lightController.artNet.artNetPakages.ArtPoll; +import model.lightController.artNet.udpListener.UdpListener; +import model.preset.PresetRepository; + + +public class ArtNetController implements LightControllable { + + private ArtNetNodeRepository artNetNodeRepository; + private UdpListener udpListener; + private PresetRepository presetRepository; + private Thread udpListenerTherad; + private byte[] dmxValues; + private int artNodePort = 6454; + private final AtomicBoolean runningUdpListener = new AtomicBoolean(false); + Gson gson; + + public ArtNetController(PresetRepository presetRepository) { + super(); + this.presetRepository = presetRepository; + this.artNetNodeRepository = new ArtNetNodeRepository(); + this.dmxValues = new byte[100]; + this.gson = new Gson(); + this.udpListener = new UdpListener(this, artNodePort, this.presetRepository, this.runningUdpListener); + + + + + //ArtNetNode artNetNode = new ArtNetNode("GlobalNode", "192.168.178.109", artNodePort); + //ArtNetNode artNetNode = new ArtNetNode("Arduino", "192.168.0.101", artNodePort); + ArtNetNode artNetNode = new ArtNetNode("Enttec", "192.168.0.100", artNodePort); + this.artNetNodeRepository.addNode(artNetNode); + } + + private void sendUdpForEachNode(byte[] udpSendData) { + this.artNetNodeRepository.getNodesMap().forEach((String name, ArtNetNode node) -> { + if(node.getNet() == 0 && node.getSubNet() == 0 && node.getUniverse() == 0 && node.isConnected()) { + try(DatagramSocket socket = new DatagramSocket()) { + InetAddress reciverIP = InetAddress.getByName(node.getIp()); + DatagramPacket outgoingDatagram = new DatagramPacket(udpSendData, udpSendData.length, reciverIP, node.getPort()); + socket.send(outgoingDatagram); + } catch (SocketException e) { + e.printStackTrace(System.err); + } catch (IOException e) { + e.printStackTrace(System.err); + } + } + }); + } + + private void sendArtCommand(ArtCommand artCommand) { + this.sendUdpForEachNode(artCommand.getUdpArray()); + } + + private void sendArtPoll() { + System.out.println("ArtPoll senden."); + byte[] udpSendData = (new ArtPoll()).getUdpArray(); + + this.artNetNodeRepository.getNodesMap().forEach((String name, ArtNetNode node) -> { + if(node.getNet() == 0 && node.getSubNet() == 0 && node.getUniverse() == 0) { + try(DatagramSocket socket = new DatagramSocket()) { + byte[] broadcast = InetAddress.getLocalHost().getAddress(); + System.out.println(InetAddress.getLocalHost()); + broadcast[3] = (byte)255; + InetAddress reciverIP = InetAddress.getByAddress(broadcast); + DatagramPacket outgoingDatagram = new DatagramPacket(udpSendData, udpSendData.length, reciverIP, node.getPort()); + socket.send(outgoingDatagram); + } catch (SocketException e) { + e.printStackTrace(System.err); + } catch (IOException e) { + e.printStackTrace(System.err); + } + } + }); + } + + private void sendDataRequest() { + System.out.println("ArtDataRequest senden."); + ArtDataRequest artDataRequest = new ArtDataRequest(ArtNetCodeLibrary.RequestCodes.DR_PRESET_STATUS.requestCode); + this.sendUdpForEachNode(artDataRequest.getUdpArray()); + } + + @Override + public void setDmxValue(int channel, byte value) + { + //System.out.println(String.format("Channel %d wurde auf %d gesetzt", channel, Byte.toUnsignedInt(value))); + this.dmxValues[channel-1] = value; + } + + @Override + public void sendDmxValue() { + //System.out.println("DMX-Daten werden gesendet."); + byte[] udpSendData = (new ArtDmx(this.dmxValues)).getUdpArray(); + + this.sendUdpForEachNode(udpSendData); + } + + @Override + public void sendDmxValue(int channel, byte value) { + this.dmxValues[channel-1] = value; + sendDmxValue(); + } + + @Override + public void activateDmxChannel(int channel) { + //System.out.println(String.format("Channel %d wurde aktiviert", channel)); + SetChannelStateCommand setChannelStateCommand = new SetChannelStateCommand(channel, true); + String command = ArtNetCodeLibrary.ArtCommands.ART_COM_SC.command + "=" + this.gson.toJson(setChannelStateCommand); + sendArtCommand(new ArtCommand(command)); + } + + @Override + public void deactivateDmxChannel(int channel) { + //System.out.println(String.format("Channel %d wurde deaktiviert", channel)); + SetChannelStateCommand setChannelStateCommand = new SetChannelStateCommand(channel, false); + String command = ArtNetCodeLibrary.ArtCommands.ART_COM_SC.command + "=" + this.gson.toJson(setChannelStateCommand); + sendArtCommand(new ArtCommand(command)); + } + + @Override + public void activateGroup(byte groupId) { + //System.out.println(String.format("Gruppe %d wurde aktiviert", Byte.toUnsignedInt(groupId))); + ActivateGroupCommand activateGroupCommand = new ActivateGroupCommand(groupId); + String command = ArtNetCodeLibrary.ArtCommands.ART_COM_AG.command + "=" + this.gson.toJson(activateGroupCommand); + sendArtCommand(new ArtCommand(command)); + } + + @Override + public void deactivateGroup(byte groupId) { + //System.out.println(String.format("Gruppe %d wurde deaktiviert", Byte.toUnsignedInt(groupId))); + DeactivateGroupCommand deactivateGroupCommand = new DeactivateGroupCommand(groupId); + String command = ArtNetCodeLibrary.ArtCommands.ART_COM_DG.command + "=" + this.gson.toJson(deactivateGroupCommand); + sendArtCommand(new ArtCommand(command)); + } + + @Override + public void activatePreset(int presetId) { + System.out.println(String.format("Preset %d wurde aktiviert", presetId)); + ActivatePresetCommand activatePresetCommand = new ActivatePresetCommand(presetId); + String command = ArtNetCodeLibrary.ArtCommands.ART_COM_AP.command + "=" + this.gson.toJson(activatePresetCommand); + sendArtCommand(new ArtCommand(command)); + } + + @Override + public void deactivatePreset(int presetId) { + System.out.println(String.format("Preset %d wurde deaktiviert", presetId)); + DeactivatePresetCommand deactivatePresetCommand = new DeactivatePresetCommand(presetId); + String command = ArtNetCodeLibrary.ArtCommands.ART_COM_DP.command + "=" + this.gson.toJson(deactivatePresetCommand); + sendArtCommand(new ArtCommand(command)); + } + + @Override + public void clearPreset(int presetId) { + System.out.println(String.format("Preset %d wird gelöscht", presetId)); + ClearPresetCommand clearPresetCommand = new ClearPresetCommand(presetId); + String command = ArtNetCodeLibrary.ArtCommands.ART_COM_CP.command + "=" + this.gson.toJson(clearPresetCommand); + sendArtCommand(new ArtCommand(command)); + } + + @Override + public void setPreset(int presetId, byte groupId, int channel, byte value) { + System.out.println(String.format("Preset %d wird gesetzt", presetId)); + SetPresetCommand setPresetCommand = new SetPresetCommand(presetId, Byte.toUnsignedInt(groupId), channel, Byte.toUnsignedInt(value)); + String command = ArtNetCodeLibrary.ArtCommands.ART_COM_SP.command + "=" + this.gson.toJson(setPresetCommand); + sendArtCommand(new ArtCommand(command)); + } + + @Override + public void connectToNodes() { + udpListenerTherad = new Thread(this.udpListener); + this.runningUdpListener.set(true); + udpListenerTherad.start(); + this.sendArtPoll(); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if(!this.artNetNodeRepository.isNodesConnected()) { + this.artNetNodeRepository.getNodesMap().forEach((String name, ArtNetNode node) -> { + if(!node.isConnected()) { + node.setConnectStatus(false); + } + }); + } + this.sendDataRequest(); + } + + public void disconnectNodes() { + this.runningUdpListener.set(false); + } + + public ArtNetNodeRepository getNodeRepository() { + return this.artNetNodeRepository; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ActivateGroupCommand.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ActivateGroupCommand.java new file mode 100644 index 0000000..db5da36 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ActivateGroupCommand.java @@ -0,0 +1,9 @@ +package model.lightController.artNet.artNetCommands; + +public class ActivateGroupCommand { + private int groupId; + + public ActivateGroupCommand(int groupId) { + this.groupId = groupId; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ActivatePresetCommand.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ActivatePresetCommand.java new file mode 100644 index 0000000..bb51cdb --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ActivatePresetCommand.java @@ -0,0 +1,9 @@ +package model.lightController.artNet.artNetCommands; + +public class ActivatePresetCommand { + private int presetId; + + public ActivatePresetCommand(int presetId) { + this.presetId = presetId; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ClearPresetCommand.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ClearPresetCommand.java new file mode 100644 index 0000000..7508a1e --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/ClearPresetCommand.java @@ -0,0 +1,9 @@ +package model.lightController.artNet.artNetCommands; + +public class ClearPresetCommand { + private int presetId; + + public ClearPresetCommand(int presetId) { + this.presetId = presetId; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/DeactivateGroupCommand.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/DeactivateGroupCommand.java new file mode 100644 index 0000000..382ccd7 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/DeactivateGroupCommand.java @@ -0,0 +1,9 @@ +package model.lightController.artNet.artNetCommands; + +public class DeactivateGroupCommand { + int groupId; + + public DeactivateGroupCommand(int groupId) { + this.groupId = groupId; + } +} \ No newline at end of file diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/DeactivatePresetCommand.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/DeactivatePresetCommand.java new file mode 100644 index 0000000..88ded60 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/DeactivatePresetCommand.java @@ -0,0 +1,9 @@ +package model.lightController.artNet.artNetCommands; + +public class DeactivatePresetCommand { + int presetId; + + public DeactivatePresetCommand(int presetId) { + this.presetId = presetId; + } +} \ No newline at end of file diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/PresetActivatedCommand.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/PresetActivatedCommand.java new file mode 100644 index 0000000..45b22bb --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/PresetActivatedCommand.java @@ -0,0 +1,15 @@ +package model.lightController.artNet.artNetCommands; + +public class PresetActivatedCommand { + private int presetId; + + public PresetActivatedCommand() {} + + public PresetActivatedCommand(int presetId) { + this.presetId = presetId; + } + + public int getPresetId() { + return this.presetId; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/PresetDeactivatedCommand.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/PresetDeactivatedCommand.java new file mode 100644 index 0000000..bf16556 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/PresetDeactivatedCommand.java @@ -0,0 +1,15 @@ +package model.lightController.artNet.artNetCommands; + +public class PresetDeactivatedCommand { + int presetId; + + public PresetDeactivatedCommand() {} + + public PresetDeactivatedCommand(int presetId) { + this.presetId = presetId; + } + + public int getPresetId() { + return presetId; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/SetChannelStateCommand.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/SetChannelStateCommand.java new file mode 100644 index 0000000..04a19b0 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/SetChannelStateCommand.java @@ -0,0 +1,11 @@ +package model.lightController.artNet.artNetCommands; + +public class SetChannelStateCommand { + int channel; + boolean isActive; + + public SetChannelStateCommand(int channel, boolean isActive) { + this.channel = channel; + this.isActive = isActive; + } +} \ No newline at end of file diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/SetPresetCommand.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/SetPresetCommand.java new file mode 100644 index 0000000..77ebaf8 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetCommands/SetPresetCommand.java @@ -0,0 +1,15 @@ +package model.lightController.artNet.artNetCommands; + +public class SetPresetCommand { + int presetId; + int groupId; + int channel; + int value; + + public SetPresetCommand(int presetId, int groupId, int channel, int value) { + this.presetId = presetId; + this.groupId = groupId; + this.channel = channel; + this.value = value; + } +} \ No newline at end of file diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetNode/ArtNetNode.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetNode/ArtNetNode.java new file mode 100644 index 0000000..7cafada --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetNode/ArtNetNode.java @@ -0,0 +1,98 @@ +package model.lightController.artNet.artNetNode; + +import java.util.concurrent.atomic.AtomicBoolean; + +import model.generic.PropertyItem; + +public class ArtNetNode extends PropertyItem{ + private String name; + private String ip; + private int port; + private byte net; + private byte subNet; + private byte universe; + private AtomicBoolean connected = new AtomicBoolean(false); + + public ArtNetNode(String name, String ip, int port) { + super(); + this.name = name; + this.ip = ip; + this.port = port; + this.net = (byte)0x00; + this.subNet = (byte)0x00; + this.universe = (byte)0x00; + } + + public ArtNetNode(String name, String ip, int port, byte net, byte subNet, byte universe) { + super(); + this.name = name; + this.ip = ip; + this.port = port; + this.net = net; + this.subNet = subNet; + this.universe = universe; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.propertySupport.firePropertyChange("name", this.name, name); + this.name = name; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.propertySupport.firePropertyChange("ip", this.ip, ip); + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.propertySupport.firePropertyChange("port", this.port, port); + this.port = port; + } + + public byte getNet() { + return net; + } + + public void setNet(byte net) { + this.propertySupport.firePropertyChange("net", this.net, net); + this.net = net; + } + + public byte getSubNet() { + return subNet; + } + + public void setSubNet(byte subNet) { + this.propertySupport.firePropertyChange("subNet", this.subNet, subNet); + this.subNet = subNet; + } + + public byte getUniverse() { + return universe; + } + + public void setUniverse(byte universe) { + this.propertySupport.firePropertyChange("universe", this.universe, universe); + this.universe = universe; + } + + public boolean isConnected() { + return this.connected.get(); + } + + public void setConnectStatus(boolean status) { + this.propertySupport.firePropertyChange("connected", this.connected, status); + this.connected.set(status); + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetNode/ArtNetNodeRepository.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetNode/ArtNetNodeRepository.java new file mode 100644 index 0000000..10ea210 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetNode/ArtNetNodeRepository.java @@ -0,0 +1,71 @@ +package model.lightController.artNet.artNetNode; + +import java.util.Map; + +import model.generic.PropertyItem; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashMap; + +public class ArtNetNodeRepository extends PropertyItem{ + private Map<String, ArtNetNode> nodes; + private boolean nodesConnected = false; + private int numberOfConnectedNodes = 0; + private PropertyChangeListener nodesListener; + + public ArtNetNodeRepository() { + super(); + nodes = new HashMap<>(); + this.nodesListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("connected")) { + if((boolean) event.getNewValue()) { + numberOfConnectedNodes++; + } else { + if(numberOfConnectedNodes >0) { + numberOfConnectedNodes--; + } + } + if(numberOfConnectedNodes == nodes.size()) { + setNodesConnected(true); + } else if(numberOfConnectedNodes == 0) { + setNodesConnected(false); + } + } + } + }; + } + + public void setNodesConnected(boolean connected) { +// if(this.nodesConnected != connected) { + + this.propertySupport.firePropertyChange("nodesConnected", null, connected); + this.nodesConnected = connected; +// } + } + + public void addNode(ArtNetNode artNetNode) { + artNetNode.addPropertyChangeListener(this.nodesListener); + nodes.put(artNetNode.getName(), artNetNode); + this.propertySupport.firePropertyChange("nodes", null, artNetNode); + } + + public void removeNode(ArtNetNode artNetNode) { + this.propertySupport.firePropertyChange("nodes", artNetNode,null); + nodes.remove(artNetNode.getName(), artNetNode); + } + + public boolean isNodesConnected() { + return this.nodesConnected; + } + + public ArtNetNode getNode(String name) { + return this.nodes.get(name); + } + + public Map<String, ArtNetNode> getNodesMap() { + return this.nodes; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtCommand.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtCommand.java new file mode 100644 index 0000000..4f077e5 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtCommand.java @@ -0,0 +1,108 @@ +package model.lightController.artNet.artNetPakages; + +import java.nio.ByteBuffer; + +public class ArtCommand { + private String id = "Art-Net" + (char)0x00; + private short opCode = ArtNetCodeLibrary.OpCodes.OP_COMMAND.opCode; + private byte protVerHi = 0x00; + private byte protVerLo = 0x0e; // Version 14; + private byte estaManHi = 0x00; + private byte estaManLo = 0x00; + private short length = 0x0000; + private String data; + + public ArtCommand(String data) { + this.length = (short) data.length(); + this.data = data; + } + + public ArtCommand(byte[] udpArray) { + byte[] lengthArray = new byte[2]; + char[] data = new char[udpArray.length - 16]; + + // protocoll version + this.protVerHi = udpArray[10]; + this.protVerLo = udpArray[11]; + + // esta manifactur + this.estaManHi = udpArray[12]; + this.estaManLo = udpArray[13]; + + // length + lengthArray[0] = udpArray[14]; + lengthArray[1] = udpArray[15]; + this.length = ByteBuffer.wrap(lengthArray).getShort(); + + for(int i=0; i < this.length; i++) + { + data[i] = (char)udpArray[16+i]; + } + this.data = String.copyValueOf(data); + } + + public byte[] getUdpArray() { + byte[] udpArray = new byte[16 + this.data.length()]; + byte[] opCodeArray = ByteBuffer.allocate(2).putShort(this.opCode).array(); + byte[] lengthArray = ByteBuffer.allocate(2).putShort(this.length).array(); + + // id + for(int i = 0; i < 8; i++) { + udpArray[i] = (byte)this.id.charAt(i); + } + + // op code + udpArray[8] = opCodeArray[1]; + udpArray[9] = opCodeArray[0]; + + // protocoll version + udpArray[10] = this.protVerHi; + udpArray[11] = this.protVerLo; + + // esta manifactur + udpArray[12] = this.estaManHi; + udpArray[13] = this.estaManLo; + + // length + udpArray[14] = lengthArray[0]; + udpArray[15] = lengthArray[1]; + + for(int i=0; i < this.length; i++) { + udpArray[16+i] = this.data.getBytes()[i]; + } + + return udpArray; + } + + public String getId() { + return id; + } + + public short getOpCode() { + return opCode; + } + + public byte getProtVerHi() { + return protVerHi; + } + + public byte getProtVerLo() { + return protVerLo; + } + + public byte getEstaManHi() { + return estaManHi; + } + + public byte getEstaManLo() { + return estaManLo; + } + + public short getLength() { + return length; + } + + public String getData() { + return data; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDataReply.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDataReply.java new file mode 100644 index 0000000..e7d388a --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDataReply.java @@ -0,0 +1,142 @@ +package model.lightController.artNet.artNetPakages; + +import java.nio.ByteBuffer; + +public class ArtDataReply { + private String id = "Art-Net" + (char)0x00; + private short opCode = ArtNetCodeLibrary.OpCodes.OP_DATA_REPLY.opCode; + private byte protVerHi = 0x00; + private byte protVerLo = 0x0e; // Version 14; + private byte estaManHi = 0x00; + private byte estaManLo = 0x00; + private byte oemHi = (byte)0xff; + private byte oemLo = (byte)0xff; + private short requestCode = 0x0000; + private short payLength = 0x0000; + private byte[] payLoad; + + public ArtDataReply(String payLoad) { + this.payLoad = payLoad.getBytes(); + } + + public ArtDataReply(byte[] udpArray) { + byte[] requestCodeArray = new byte[2]; + byte[] payLengthArray = new byte[2]; + + // protocoll version + this.protVerHi = udpArray[10]; + this.protVerLo = udpArray[11]; + + // esta manifactur + this.estaManHi = udpArray[12]; + this.estaManLo = udpArray[13]; + + // oem + this.oemHi = udpArray[14]; + this.oemLo = udpArray[15]; + + // request code + requestCodeArray[1] = udpArray[16]; + requestCodeArray[0] = udpArray[17]; + this.requestCode = ByteBuffer.wrap(requestCodeArray).getShort(); + + // length + payLengthArray[0] = udpArray[18]; + payLengthArray[1] = udpArray[19]; + this.payLength = ByteBuffer.wrap(payLengthArray).getShort(); + + // payload + byte[] payLoadArray = new byte[this.payLength]; + for(int i=0; i < this.payLength; i++) { + payLoadArray[i] = udpArray[20+i]; + } + this.payLoad = payLoadArray; + } + + public byte[] getUdpArray() { + byte[] udpArray = new byte[20 + this.payLength]; + byte[] opCodeArray = ByteBuffer.allocate(2).putShort(this.opCode).array(); + byte[] requestCodeArray = ByteBuffer.allocate(2).putShort(this.requestCode).array(); + byte[] payLengthArray = ByteBuffer.allocate(2).putShort(this.payLength).array(); + + // id + for(int i = 0; i < 8; i++) { + udpArray[i] = (byte)this.id.charAt(i); + } + + // op code + udpArray[8] = opCodeArray[1]; + udpArray[9] = opCodeArray[0]; + + // protocoll version + udpArray[10] = this.protVerHi; + udpArray[11] = this.protVerLo; + + // esta manifactur + udpArray[12] = this.estaManHi; + udpArray[13] = this.estaManLo; + + // oem + udpArray[14] = this.oemHi; + udpArray[15] = this.oemLo; + + // request code + udpArray[16] = requestCodeArray[1]; + udpArray[17] = requestCodeArray[0]; + + // length + udpArray[18] = payLengthArray[1]; + udpArray[19] = payLengthArray[0]; + + // payload + for(int i=0; i < this.payLength; i++) { + udpArray[20+i] = this.payLoad[i]; + } + + return udpArray; + } + + public String getId() { + return id; + } + + public short getOpCode() { + return opCode; + } + + public byte getProtVerHi() { + return protVerHi; + } + + public byte getProtVerLo() { + return protVerLo; + } + + public byte getEstaManHi() { + return estaManHi; + } + + public byte getEstaManLo() { + return estaManLo; + } + + public byte getOemHi() { + return oemHi; + } + + public byte getOemLo() { + return oemLo; + } + + public short getRequestCode() { + return requestCode; + } + + public short getPayLength() { + return payLength; + } + + public byte[] getPayLoad() { + return payLoad; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDataRequest.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDataRequest.java new file mode 100644 index 0000000..cb3642a --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDataRequest.java @@ -0,0 +1,109 @@ +package model.lightController.artNet.artNetPakages; + +import java.nio.ByteBuffer; + +public class ArtDataRequest { + private String id = "Art-Net" + (char)0x00; + private short opCode = ArtNetCodeLibrary.OpCodes.OP_DATA_REQUEST.opCode; + private byte protVerHi = 0x00; + private byte protVerLo = 0x0e; // Version 14; + private byte estaManHi = 0x00; + private byte estaManLo = 0x00; + private byte oemHi = (byte)0xff; + private byte oemLo = (byte)0xff; + private short requestCode = 0x0000; + + public ArtDataRequest(short requestCode) { + this.requestCode = requestCode; + } + + public ArtDataRequest(byte[] udpArray) { + byte[] requestCodeArray = new byte[2]; + + // protocoll version + this.protVerHi = udpArray[10]; + this.protVerLo = udpArray[11]; + + // esta manifactur + this.estaManHi = udpArray[12]; + this.estaManLo = udpArray[13]; + + // oem + this.oemHi = udpArray[14]; + this.oemLo = udpArray[15]; + + // request code + requestCodeArray[0] = udpArray[16]; + requestCodeArray[1] = udpArray[17]; + this.requestCode = ByteBuffer.wrap(requestCodeArray).getShort(); + } + + public byte[] getUdpArray() { + byte[] udpArray = new byte[18]; + byte[] opCodeArray = ByteBuffer.allocate(2).putShort(this.opCode).array(); + byte[] requestCodeArray = ByteBuffer.allocate(2).putShort(this.requestCode).array(); + + // id + for(int i = 0; i < 8; i++) { + udpArray[i] = (byte)this.id.charAt(i); + } + + // op code + udpArray[8] = opCodeArray[1]; + udpArray[9] = opCodeArray[0]; + + // protocoll version + udpArray[10] = this.protVerHi; + udpArray[11] = this.protVerLo; + + // esta manifactur + udpArray[12] = this.estaManHi; + udpArray[13] = this.estaManLo; + + // oem + udpArray[14] = this.oemHi; + udpArray[15] = this.oemLo; + + // request code + udpArray[16] = requestCodeArray[0]; + udpArray[17] = requestCodeArray[1]; + + return udpArray; + } + + public String getId() { + return id; + } + + public short getOpCode() { + return opCode; + } + + public byte getProtVerHi() { + return protVerHi; + } + + public byte getProtVerLo() { + return protVerLo; + } + + public byte getEstaManHi() { + return estaManHi; + } + + public byte getEstaManLo() { + return estaManLo; + } + + public byte getOemHi() { + return oemHi; + } + + public byte getOemLo() { + return oemLo; + } + + public short getRequestCode() { + return requestCode; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDmx.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDmx.java new file mode 100644 index 0000000..2cacffe --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtDmx.java @@ -0,0 +1,74 @@ +package model.lightController.artNet.artNetPakages; + +import java.nio.ByteBuffer; + +public class ArtDmx { + private String id = "Art-Net" + ((char)0x00); + private short opCode = ArtNetCodeLibrary.OpCodes.OP_DMX.opCode; + private byte protVerHi = 0x00; + private byte protVerLo = 0x10; // Version 14; + private byte sequence = 0x00; + private byte physical = 0x00; + private byte subUni = 0x00; + private byte net = 0x00; + private short length = 0x0200; + private byte[] data; + + public ArtDmx(byte[] data) { + this.length = (short)data.length; + this.data = data; + } + + public ArtDmx(byte[] data, byte net, byte subNet, byte universe) { + this.net = net; + + byte firstPart = (byte) (subNet & 0b0001_1111); + byte secondPart = (byte) (universe & 0b0000_0111); + this.subUni = (byte) ((firstPart << 3) | secondPart); + + this.length = (short)data.length; + this.data = data; + } + + public byte[] getUdpArray() { + byte[] udpArray = new byte[18 + this.length]; + byte[] opCodeArray = ByteBuffer.allocate(2).putShort(this.opCode).array(); + byte[] lengthArray = ByteBuffer.allocate(2).putShort(this.length).array(); + + // id + for(int i = 0; i < 8; i++) { + udpArray[i] = (byte)this.id.charAt(i); + } + + // op code + udpArray[8] = opCodeArray[1]; + udpArray[9] = opCodeArray[0]; + + // protocoll version + udpArray[10] = this.protVerHi; + udpArray[11] = this.protVerLo; + + // sequence + udpArray[12] = this.sequence; + + // physical + udpArray[13] = this.physical; + + // sub-net and universe + udpArray[14] = this.subUni; + + // net + udpArray[15] = this.net; + + // length + udpArray[16] = lengthArray[0]; + udpArray[17] = lengthArray[1]; + + for(int i = 0; i < this.length; i++) { + + udpArray[18 + i] = this.data[i]; + } + + return udpArray; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtNetCodeLibrary.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtNetCodeLibrary.java new file mode 100644 index 0000000..7e8427b --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtNetCodeLibrary.java @@ -0,0 +1,53 @@ +package model.lightController.artNet.artNetPakages; + +public class ArtNetCodeLibrary { + public static enum OpCodes { + OP_POLL((short)0x2000), + OP_POLL_REPLY((short)0x2100), + OP_COMMAND((short)0x2400), + OP_DATA_REQUEST((short)0x2700), + OP_DATA_REPLY((short)0x2800), + OP_DMX((short)0x5000); + + public final short opCode; + + private OpCodes(short opCode) { + this.opCode = opCode; + } + } + + public static enum RequestCodes { + DR_POLL ((short)0x0000), + DR_URL_PRODUCT ((short)0x0001), + DR_URL_USER_GUIDE ((short)0x0002), + DR_URL_SUPPORT ((short)0x0003), + DR_URL_PERS_UDR ((short)0x0004), + DR_URL_PERS_GDTF ((short)0x0005), + DR_PRESET_STATUS ((short)0x8000); + + public final short requestCode; + + private RequestCodes(short requestCode) { + this.requestCode = requestCode; + } + } + + public static enum ArtCommands { + ART_COM_AP("activatePreset"), + ART_COM_DP("deactivatePreset"), + ART_COM_CP("clearPreset"), + ART_COM_SP("setPreset"), + ART_COM_SC("setChannelState"), + ART_COM_AG("activateGroup"), + ART_COM_DG("deactivateGroup"), + ART_COM_PA("presetActivated"), + ART_COM_PD("presetDeactivated"); + + public final String command; + + private ArtCommands(String command) { + this.command = command; + } + } + +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtPoll.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtPoll.java new file mode 100644 index 0000000..7f08eb5 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtPoll.java @@ -0,0 +1,62 @@ +package model.lightController.artNet.artNetPakages; + +import java.nio.ByteBuffer; + +public class ArtPoll { + private String id = "Art-Net" + (char)0x00; + private short opCode = ArtNetCodeLibrary.OpCodes.OP_POLL.opCode; + private byte protVerHi = 0x00; + private byte protVerLo = 0x0E; // Version 14 + private byte flags = 0x00; + private byte diagPriority = 0x00; + private byte targetPortAddressTopHi = 0x00; + private byte targetPortAddressTopLo = 0x00; + private byte targetPortAddressBottomHi = 0x00; + private byte targetPortAddressBottomLo = 0x00; + private byte estaManHi = 0x00; + private byte estaManLo = 0x00; + private byte oemHi = (byte)0xff; + private byte oemLo = (byte)0xff; + + public ArtPoll() {} + + public byte[] getUdpArray() { + byte[] udpArray = new byte[22]; + byte[] opCodeArray = ByteBuffer.allocate(2).putShort(this.opCode).array(); + + // id + for(int i = 0; i < 8; i++) { + udpArray[i] = (byte)this.id.charAt(i); + } + + // op code + udpArray[8] = opCodeArray[1]; + udpArray[9] = opCodeArray[0]; + + // protocoll version + udpArray[10] = this.protVerHi; + udpArray[11] = this.protVerLo; + + // flag + udpArray[12] = this.flags; + + // diagonstic prio + udpArray[13] = this.diagPriority; + + // target port + udpArray[14] = this.targetPortAddressTopHi; + udpArray[15] = this.targetPortAddressTopLo; + udpArray[16] = this.targetPortAddressBottomHi; + udpArray[17] = this.targetPortAddressBottomLo; + + // esta manifacture + udpArray[18] = this.estaManHi; + udpArray[19] = this.estaManLo; + + // oem + udpArray[20] = this.oemHi; + udpArray[21] = this.oemLo; + + return udpArray; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtPollReply.java b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtPollReply.java new file mode 100644 index 0000000..23f399c --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/artNetPakages/ArtPollReply.java @@ -0,0 +1,543 @@ +package model.lightController.artNet.artNetPakages; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; + +public class ArtPollReply { + private String id = "Art-Net" + (char)0x00; + private short opCode = ArtNetCodeLibrary.OpCodes.OP_POLL_REPLY.opCode; + private byte ipAddress[] = {0x00, 0x00, 0x00, 0x00}; + private short port = 0x1936; + private byte versInfoH = 0x00; + private byte versInfoL = 0x01; + private byte netSwitch = 0x00; + private byte subSwitch = 0x00; + private byte oemHi = (byte)0xff; + private byte oemLo = (byte)0xff; + private byte ubeaVersion = 0x00; + private byte status1 = 0x00; + private byte estaManLo = 0x00; + private byte estaManHi = 0x00; + private byte portName[] = {'K', 'I', 'W', 'I', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + private byte longName[] = new byte[64]; + private byte nodeReport[] = new byte[64]; + private byte numPortsHi = 0x00; + private byte numPortsLo = 0x01; + private byte portTypes[] = {0x00, 0x00, 0x00, 0x00}; + private byte goodInput[] = {0x04, 0x04, 0x04, 0x04}; + private byte goodOutputA [] = {0x00, 0x00, 0x00, 0x00}; + private byte swIn[] = {0x00, 0x00, 0x00, 0x00}; + private byte swOut[] = {0x00, 0x00, 0x00, 0x00}; + private byte acnPriority = 0x00; + private byte swMacro = 0x00; + private byte swRemote = 0x00; + private byte style = 0x00; + private byte mac[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + private byte bindIp[] = {0x00, 0x00, 0x00, 0x00}; + private byte bindIndex = 0x00; + private byte status2 = 0x00; + private byte goodOutputB[] = {0x00, 0x00, 0x00, 0x00}; + private byte status3 = 0x00; + private byte defaulRespUID[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + private byte userHi = 0x00; + private byte userLo = 0x00; + private byte refreshRateHi = 0x00; + private byte refreshRateLo = 0x2C; + + public ArtPollReply() { + try { + InetAddress localHost = InetAddress.getLocalHost(); + this.ipAddress = localHost.getAddress(); + this.mac = NetworkInterface.getByInetAddress(localHost).getHardwareAddress(); + } catch (UnknownHostException | SocketException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + byte[] longName = "KIWI (is a) Illumination Weeny Interface".getBytes(); + for(int i = 0; i < longName.length; i++) { + this.longName[i] = longName[i]; + } + } + + public ArtPollReply(byte[] udpArray) { + byte[] portArray = new byte[2]; + + if(udpArray.length >= 207) { + + // node ip + for(int i= 0; i < 4; i++) + { + this.ipAddress[i] = udpArray[10+i]; + } + + // port + portArray[1] = udpArray[14]; + portArray[0] = udpArray[15]; + this.port = ByteBuffer.wrap(portArray).getShort(); + + // version info + this.versInfoH = udpArray[16]; + this.versInfoL = udpArray[17]; + + // net address + this.netSwitch = udpArray[18]; + + // sub net address + this.subSwitch = udpArray[19]; + + // oem + this.oemHi = udpArray[20]; + this.oemLo = udpArray[21]; + + // ubea version + this.ubeaVersion = udpArray[22]; + + // status + this.status1 = udpArray[23]; + + // esta + this.estaManLo = udpArray[24]; + this.estaManHi = udpArray[25]; + + // port name + for (int i = 0; i < 18; i++) + { + this.portName[i] = udpArray[26+i]; + } + + // long name + for (int i = 0; i < 64; i++) + { + this.longName[i] = udpArray[44+i]; + } + + // node report + for (int i = 0; i < 64; i++) + { + this.nodeReport[i] = udpArray[108+i]; + } + + // number of ports + this.numPortsHi = udpArray[172]; + this.numPortsLo = udpArray[173]; + + // port type + for (int i = 0; i < 4; i++) + { + this.portTypes[i] = udpArray[174+i]; + } + + // good inputs + for (int i = 0; i < 4; i++) + { + this.goodInput[i] = udpArray[178+i]; + } + + // good output A + for (int i = 0; i < 4; i++) + { + this.goodOutputA[i] = udpArray[182+i]; + } + + // sw in + for (int i = 0; i < 4; i++) + { + this.swIn[i] = udpArray[186+i]; + } + + // sw out + for (int i = 0; i < 4; i++) + { + this.swOut[i] = udpArray[190+i]; + } + + // prio acn + this.acnPriority = udpArray[194]; + + // sw macro + this.swMacro = udpArray[195]; + + // sw remote + this.swRemote = udpArray[196]; + + // style + this.style = udpArray[200]; + + // mac address + for (int i = 0; i < 6; i++) + { + this.mac[i] = udpArray[201+i]; + } + } + if(udpArray.length >= 208) { + // bind ip + for (int i = 0; i < 4; i++) { + this.bindIp[i] = udpArray[207+i]; + } + } + if(udpArray.length >= 212) { + // bind index + this.bindIndex = udpArray[211]; + } + if(udpArray.length >= 213) { + // status 2 + this.status2 = udpArray[212]; + } + if(udpArray.length >= 214) { + // good output b + for (int i = 0; i < 4; i++) + { + this.goodOutputB[i] = udpArray[213+i]; + } + } + if(udpArray.length >= 218) { + // status 3 + this.status3 = udpArray[217]; + } + if(udpArray.length >= 219) { + // default responder uid + for (int i = 0; i < 6; i++) + { + this.defaulRespUID[i] = udpArray[218+i]; + } + } + if(udpArray.length >= 225) { + // user data + this.userHi = udpArray[224]; + this.userLo = udpArray[225]; + } + if(udpArray.length >= 227) { + // user data + this.refreshRateHi = udpArray[226]; + this.refreshRateLo = udpArray[227]; + } + } + + public byte[] getUdpArray() { + byte[] udpArray = new byte[236]; + byte[] opCodeArray = ByteBuffer.allocate(2).putShort(this.opCode).array(); + byte[] portArray = ByteBuffer.allocate(2).putShort(this.port).array(); + + // id + for(int i = 0; i < 8; i++) { + udpArray[i] = (byte)this.id.charAt(i); + } + + // op code + udpArray[8] = opCodeArray[1]; + udpArray[9] = opCodeArray[0]; + + // node ip + for(int i= 0; i < 4; i++) + { + udpArray[10+i] = this.ipAddress[i]; + } + + // port + udpArray[14] = portArray[1]; + udpArray[15] = portArray[0]; + + // version info + udpArray[16] = this.versInfoH; + udpArray[17] = this.versInfoL; + + // net address + udpArray[18] = this.netSwitch; + + // sub net address + udpArray[19] = this.subSwitch; + + // oem + udpArray[20] = this.oemHi; + udpArray[21] = this.oemLo; + + // ubea version + udpArray[22] = this.ubeaVersion; + + // status + udpArray[23] = this.status1; + + // esta + udpArray[24] = this.estaManLo; + udpArray[25] = this.estaManHi; + + // port name + for (int i = 0; i < 18; i++) + { + udpArray[26+i] = this.portName[i]; + } + + // long name + for (int i = 0; i < 64; i++) + { + udpArray[44+i] = this.longName[i]; + } + + // node report + for (int i = 0; i < 64; i++) + { + udpArray[108+i] = this.nodeReport[i]; + } + + // number of ports + udpArray[172] = this.numPortsHi; + udpArray[173] = this.numPortsLo; + + // port type + for (int i = 0; i < 4; i++) + { + udpArray[174+i] = this.portTypes[i]; + } + + // good inputs + for (int i = 0; i < 4; i++) + { + udpArray[178+i] = this.goodInput[i]; + } + + // good output A + for (int i = 0; i < 4; i++) + { + udpArray[182+i] = this.goodOutputA[i]; + } + + // sw in + for (int i = 0; i < 4; i++) + { + udpArray[186+i] = this.swIn[i]; + } + + // sw out + for (int i = 0; i < 4; i++) + { + udpArray[190+i] = this.swOut[i]; + } + + // prio acn + udpArray[194] = this.acnPriority; + + // sw macro + udpArray[195] = this.swMacro; + + // sw remote + udpArray[196] = this.swRemote; + + // spare + for (int i = 0; i < 3; i++) + { + udpArray[197+i] = 0x00; + } + + // style + udpArray[200] = this.style; + + // mac address + for (int i = 0; i < 6; i++) + { + udpArray[201+i] = this.mac[i]; + } + + // bind ip + for (int i = 0; i < 4; i++) + { + udpArray[207+i] = this.bindIp[i]; + } + + // bind index + udpArray[211] = this.bindIndex; + + // status 2 + udpArray[212] = this.status2; + + // good output b + for (int i = 0; i < 4; i++) + { + udpArray[213+i] = this.goodOutputB[i]; + } + + // status 3 + udpArray[217] = this.status3; + + // default responder uid + for (int i = 0; i < 6; i++) + { + udpArray[218+i] = this.defaulRespUID[i]; + } + + // user data + udpArray[224] = this.userHi; + udpArray[225] = this.userLo; + + // user data + udpArray[226] = this.refreshRateHi; + udpArray[227] = this.refreshRateLo; + + // filler + for (int i = 0; i < 8; i++) + { + udpArray[228+i] = 0x00; + } + + return udpArray; + } + + public String getId() { + return id; + } + + public short getOpCode() { + return opCode; + } + + public byte[] getIpAddress() { + return ipAddress; + } + + public short getPort() { + return port; + } + + public byte getVersInfoH() { + return versInfoH; + } + + public byte getVersInfoL() { + return versInfoL; + } + + public byte getNetSwitch() { + return netSwitch; + } + + public byte getSubSwitch() { + return subSwitch; + } + + public byte getOemHi() { + return oemHi; + } + + public byte getOemLo() { + return oemLo; + } + + public byte getUbeaVersion() { + return ubeaVersion; + } + + public byte getStatus1() { + return status1; + } + + public byte getEstaManLo() { + return estaManLo; + } + + public byte getEstaManHi() { + return estaManHi; + } + + public byte[] getPortName() { + return portName; + } + + public byte[] getLongName() { + return longName; + } + + public byte[] getNodeReport() { + return nodeReport; + } + + public byte getNumPortsHi() { + return numPortsHi; + } + + public byte getNumPortsLo() { + return numPortsLo; + } + + public byte[] getPortTypes() { + return portTypes; + } + + public byte[] getGoodInput() { + return goodInput; + } + + public byte[] getGoodOutputA() { + return goodOutputA; + } + + public byte[] getSwIn() { + return swIn; + } + + public byte[] getSwOut() { + return swOut; + } + + public byte getAcnPriority() { + return acnPriority; + } + + public byte getSwMacro() { + return swMacro; + } + + public byte getSwRemote() { + return swRemote; + } + + public byte getStyle() { + return style; + } + + public byte[] getMac() { + return mac; + } + + public byte[] getBindIp() { + return bindIp; + } + + public byte getBindIndex() { + return bindIndex; + } + + public byte getStatus2() { + return status2; + } + + public byte[] getGoodOutputB() { + return goodOutputB; + } + + public byte getStatus3() { + return status3; + } + + public byte[] getDefaulRespUID() { + return defaulRespUID; + } + + public byte getUserHi() { + return userHi; + } + + public byte getUserLo() { + return userLo; + } + + public byte getRefreshRateHi() { + return refreshRateHi; + } + + public byte getRefreshRateLo() { + return refreshRateLo; + } +} diff --git a/src/lightControlSoftware/src/model/lightController/artNet/udpListener/UdpListener.java b/src/lightControlSoftware/src/model/lightController/artNet/udpListener/UdpListener.java new file mode 100644 index 0000000..6728b5e --- /dev/null +++ b/src/lightControlSoftware/src/model/lightController/artNet/udpListener/UdpListener.java @@ -0,0 +1,159 @@ +package model.lightController.artNet.udpListener; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; +import com.google.gson.Gson; + +import model.lightController.artNet.ArtNetController; +import model.lightController.artNet.artNetCommands.PresetActivatedCommand; +import model.lightController.artNet.artNetCommands.PresetDeactivatedCommand; +import model.lightController.artNet.artNetNode.ArtNetNode; +import model.lightController.artNet.artNetPakages.ArtCommand; +import model.lightController.artNet.artNetPakages.ArtDataReply; +import model.lightController.artNet.artNetPakages.ArtNetCodeLibrary; +import model.lightController.artNet.artNetPakages.ArtPollReply; +import model.preset.PresetRepository; + +public class UdpListener implements Runnable{ + + private ArtNetController artNetController; + private PresetRepository presetRepository; + private int port = 6454; + private byte[] opCodeArray = new byte[2]; + private short opCode; + byte[] incomingData; + AtomicBoolean running; + Gson gson; + + public UdpListener(ArtNetController artNetController, int port, PresetRepository presetRepository, AtomicBoolean running) { + this.artNetController = artNetController; + this.presetRepository = presetRepository; + this.running = running; + incomingData = new byte[550]; + this.gson = new Gson(); + } + + private boolean checkIfArtNetPakage(byte[] incomingData) { + boolean isArtNet = true; + byte[] artNetString = ("Art-Net" + (char)0x00).getBytes(); + + for(int i = 0; i<8; i++) { + if(artNetString[i] != incomingData[i]) { + isArtNet = false; + } + } + return isArtNet; + } + + private String convertIp(byte[] ip) { + String ipString = ""; + for(int i = 0; i<3; i++) { + ipString += Byte.toUnsignedInt(ip[i]) + "."; + } + ipString += Byte.toUnsignedInt(ip[3]); + return ipString; + } + + private void sendArtPollReply(InetAddress reciverIP) { + byte[] udpSendData = (new ArtPollReply()).getUdpArray(); + + try(DatagramSocket socket = new DatagramSocket()) { + DatagramPacket outgoingDatagram = new DatagramPacket(udpSendData, udpSendData.length, reciverIP, this.port); + socket.send(outgoingDatagram); + } catch (SocketException e) { + e.printStackTrace(System.err); + } catch (IOException e) { + e.printStackTrace(System.err); + } + } + + private void processArtPollReply() { + System.out.println("ArtPollReply empfangen"); + ArtPollReply artPollReply = new ArtPollReply(incomingData); + artNetController.getNodeRepository().getNodesMap().forEach((String name, ArtNetNode node) -> { + if(convertIp(artPollReply.getIpAddress()).equals(node.getIp())) { + node.setConnectStatus(true); + } + }); + } + + private void processArtCommand() { + ArtCommand artCommand = new ArtCommand(incomingData); + String commandsString = artCommand.getData(); + String[] commands = commandsString.split("&"); + for(String commandString : commands) { + String command = commandString.split("=")[0]; + String commandJSON = commandString.split("=")[1].trim(); + + if(command.equals(ArtNetCodeLibrary.ArtCommands.ART_COM_PA.command)) { + PresetActivatedCommand presetActivatedCommand = this.gson.fromJson(commandJSON, PresetActivatedCommand.class); + presetRepository.getPreset(presetActivatedCommand.getPresetId()).activate(); + + } else if(command.equals(ArtNetCodeLibrary.ArtCommands.ART_COM_PD.command)) { + PresetDeactivatedCommand presetDeactivatedCommand = this.gson.fromJson(commandJSON, PresetDeactivatedCommand.class); + presetRepository.getPreset(presetDeactivatedCommand.getPresetId()).deactivate(); + } + } + } + + private void processArtDataReply() { + ArtDataReply artDataReply = new ArtDataReply(incomingData); + if(artDataReply.getRequestCode() == ArtNetCodeLibrary.RequestCodes.DR_PRESET_STATUS.requestCode) { + for(int i = 0; i < (int)artDataReply.getPayLength(); i++) { + if(artDataReply.getPayLoad()[i] == 1) { + presetRepository.getPreset(i).activate(); + } + } + } + } + + @Override + public void run() { + + try(DatagramSocket socket = new DatagramSocket(this.port)) { + socket.setSoTimeout(1000); + while(this.running.get()) { + try { + DatagramPacket incomingDatagram = new DatagramPacket(this.incomingData, this.incomingData.length); + socket.receive(incomingDatagram); + this.incomingData = incomingDatagram.getData(); + + if(checkIfArtNetPakage(this.incomingData)) { + opCodeArray[0] = this.incomingData[9]; + opCodeArray[1] = this.incomingData[8]; + this.opCode = ByteBuffer.wrap(opCodeArray).getShort(); + if(this.opCode == ArtNetCodeLibrary.OpCodes.OP_POLL.opCode) { + //System.out.println("Art Poll empfangen."); + this.sendArtPollReply(incomingDatagram.getAddress()); + }else if(this.opCode == ArtNetCodeLibrary.OpCodes.OP_POLL_REPLY.opCode) { + //System.out.println("Art Poll Reply empfangen."); + this.processArtPollReply(); + } else if(this.opCode == ArtNetCodeLibrary.OpCodes.OP_COMMAND.opCode) { + System.out.println("Art Command empfangen."); + this.processArtCommand(); + } else if(this.opCode == ArtNetCodeLibrary.OpCodes.OP_DATA_REPLY.opCode) { + //System.out.println("Art Data Reply empfangen."); + this.processArtDataReply(); + } + } + } catch (SocketTimeoutException e) {} + } + socket.close(); + } catch (SocketException e) { + e.printStackTrace(System.err); + } catch (IOException e) { + e.printStackTrace(System.err); + } finally { + artNetController.getNodeRepository().getNodesMap().forEach((String name, ArtNetNode node) -> { + node.setConnectStatus(false); + }); + } + } + +} diff --git a/src/lightControlSoftware/src/model/lightingScene/LightingScene.java b/src/lightControlSoftware/src/model/lightingScene/LightingScene.java new file mode 100644 index 0000000..cc3418c --- /dev/null +++ b/src/lightControlSoftware/src/model/lightingScene/LightingScene.java @@ -0,0 +1,12 @@ +package model.lightingScene; + +public interface LightingScene { + public static enum LightingSceneType + { + LIGHTING_MOOD, + COMPOSITION + }; + public LightingSceneType getLightingSceneType(); + public void activate(); + public void deactivate(); +} diff --git a/src/lightControlSoftware/src/model/lightingScene/composition/Composition.java b/src/lightControlSoftware/src/model/lightingScene/composition/Composition.java new file mode 100644 index 0000000..65a1dfc --- /dev/null +++ b/src/lightControlSoftware/src/model/lightingScene/composition/Composition.java @@ -0,0 +1,72 @@ +package model.lightingScene.composition; + +import java.util.Map; + +import model.fixture.DmxValues; +import model.generic.PropertyItem; +import model.lightingScene.LightingScene; + +import java.util.HashMap; + +public class Composition extends PropertyItem implements LightingScene{ + private String name; + private boolean isActive; + private Map<String, DmxValues> fixtureValues; + + public Composition(String name) { + super(); + this.name = name; + this.isActive = false; + this.fixtureValues = new HashMap<>(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + String nameOld = this.name; + this.name = name; + propertySupport.firePropertyChange("name", nameOld, name); + } + + public boolean isActive() { + return this.isActive; + } + + public void setDmxValuesForFixture(String fixtureName, DmxValues values) { + propertySupport.firePropertyChange("setFixtureValues", fixtureName, values); + fixtureValues.put(fixtureName, values); + } + + public void removeFixtureFromComposition(String fixtureName) { + propertySupport.firePropertyChange("removeFixtureValues", fixtureName, null); + fixtureValues.remove(fixtureName); + } + + public DmxValues getDmxValuesForFixture(String fixtureName) { + return fixtureValues.get(fixtureName); + } + + @Override + public void activate() { + boolean activeOld = this.isActive; + this.isActive = true; + propertySupport.firePropertyChange("isActive", activeOld, true); + } + + @Override + public void deactivate() { + boolean activeOld = this.isActive; + this.isActive = false; + propertySupport.firePropertyChange("isActive", activeOld, false); + } + + public LightingSceneType getLightingSceneType() { + return LightingSceneType.COMPOSITION; + } + + public Map<String, DmxValues> getFixtureValues() { + return this.fixtureValues; + } +} diff --git a/src/lightControlSoftware/src/model/lightingScene/composition/CompositionRepository.java b/src/lightControlSoftware/src/model/lightingScene/composition/CompositionRepository.java new file mode 100644 index 0000000..3e0edf8 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightingScene/composition/CompositionRepository.java @@ -0,0 +1,33 @@ +package model.lightingScene.composition; + +import java.util.Map; + +import model.generic.PropertyItem; + +import java.util.HashMap; + +public class CompositionRepository extends PropertyItem { + private Map<String, Composition> compositions; + + public CompositionRepository() { + this.compositions = new HashMap<>(); + } + + public void addComposition(Composition composition) { + this.compositions.put(composition.getName(), composition); + this.propertySupport.firePropertyChange("compositions", null, composition); + } + + public Composition getComposition(String name) { + return compositions.get(name); + } + + public void removeComposition(String name) { + this.propertySupport.firePropertyChange("compositions", this.compositions.get(name), null); + this.compositions.remove(name); + } + + public Map<String, Composition> getCompositionMap() { + return this.compositions; + } +} diff --git a/src/lightControlSoftware/src/model/lightingScene/lightingMood/LightingMood.java b/src/lightControlSoftware/src/model/lightingScene/lightingMood/LightingMood.java new file mode 100644 index 0000000..3f03119 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightingScene/lightingMood/LightingMood.java @@ -0,0 +1,60 @@ +package model.lightingScene.lightingMood; + +import model.fixture.DmxValues; +import model.generic.PropertyItem; +import model.lightingScene.LightingScene; + +public class LightingMood extends PropertyItem implements LightingScene{ + private String name; + private boolean isActive; + private DmxValues dmxValues; + + public LightingMood(String name) { + super(); + this.name = name; + this.isActive = false; + this.dmxValues = new DmxValues(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + propertySupport.firePropertyChange("name", this.name, name); + this.name = name; + } + + public boolean isActive() { + return this.isActive; + } + + public DmxValues getDmxValues() { + return this.dmxValues; + } + + public void setDmxValues(DmxValues dmxValues) { + propertySupport.firePropertyChange("dmxValues", this.dmxValues, dmxValues); + this.dmxValues = dmxValues; + } + + @Override + public void activate() { + if(!this.isActive) { + this.isActive = true; + propertySupport.firePropertyChange("isActive", false, true); + } + + + } + + @Override + public void deactivate() { + this.isActive = false; + propertySupport.firePropertyChange("isActive", true, false); + } + + public LightingSceneType getLightingSceneType() { + return LightingSceneType.LIGHTING_MOOD; + } +} diff --git a/src/lightControlSoftware/src/model/lightingScene/lightingMood/LightingMoodRepository.java b/src/lightControlSoftware/src/model/lightingScene/lightingMood/LightingMoodRepository.java new file mode 100644 index 0000000..9231ee7 --- /dev/null +++ b/src/lightControlSoftware/src/model/lightingScene/lightingMood/LightingMoodRepository.java @@ -0,0 +1,38 @@ +package model.lightingScene.lightingMood; + + +import java.util.Map; + +import model.generic.PropertyItem; + +import java.util.HashMap; + +public class LightingMoodRepository extends PropertyItem { + private Map<String, LightingMood> lightingMoods; + + public LightingMoodRepository() { + this.lightingMoods = new HashMap<>(); + } + + public void addLightingMood(LightingMood lightingMood) { + if(this.lightingMoods.containsKey(lightingMood.getName())) { + throw new IllegalArgumentException("Es existiert bereits eine Lichtstimmung mit diesem Namen"); + } + + this.lightingMoods.put(lightingMood.getName(), lightingMood); + this.propertySupport.firePropertyChange("lightingMoods", null, lightingMood); + } + + public LightingMood getLightingMood(String name) { + return lightingMoods.get(name); + } + + public void removeLightingMood(String name) { + this.propertySupport.firePropertyChange("lightingMoods", this.lightingMoods.get(name), null); + this.lightingMoods.remove(name); + } + + public Map<String, LightingMood> getLightingMoodMap() { + return this.lightingMoods; + } +} \ No newline at end of file diff --git a/src/lightControlSoftware/src/model/preset/Preset.java b/src/lightControlSoftware/src/model/preset/Preset.java new file mode 100644 index 0000000..5192734 --- /dev/null +++ b/src/lightControlSoftware/src/model/preset/Preset.java @@ -0,0 +1,112 @@ +package model.preset; + +import model.KiwiModelHandler; +import model.fixture.DmxValues; +import model.fixture.Fixture; +import model.generic.PropertyItem; +import model.group.Group; +import model.lightController.LightControllable; +import model.lightingScene.LightingScene; +import model.lightingScene.LightingScene.LightingSceneType; +import model.lightingScene.composition.Composition; +import model.lightingScene.lightingMood.LightingMood; + +public class Preset extends PropertyItem{ + private static int numberOfPresets = 0; + private int id; + private Group group; + private LightingScene lightingScene; + private State state = State.DEAKTIV; + private LightControllable lightController = KiwiModelHandler.getLightController(); + + public static enum State { + AKTIV, + PARTAKTIV, + DEAKTIV + } + + public Preset() { + this.id = Preset.numberOfPresets; + Preset.numberOfPresets++; + } + + public Preset(Group group, LightingScene lightingScene) { + super(); + this.id = Preset.numberOfPresets; + this.group = group; + this.lightingScene = lightingScene; + Preset.numberOfPresets++; + } + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } + + public LightingScene getLightingScene() { + return lightingScene; + } + + public void setLightingScene(LightingScene lightingScene) { + this.lightingScene = lightingScene; + } + + public int getId() { + return id; + } + + public State getState() { + return state; + } + + private void setState(State state) { + this.propertySupport.firePropertyChange("state", this.state, state); + this.state = state; + } + + public void activate() { + this.setState(State.AKTIV); + this.group.setCurrentState(Group.State.AKTIV); + this.lightingScene.activate(); + } + + public void deactivate() { + this.setState(State.DEAKTIV); + this.group.setCurrentState(Group.State.DEAKTIV); + this.lightingScene.deactivate(); + } + + public void updatePreset() { + if(lightingScene != null) { + this.lightController.clearPreset(this.id); + if(lightingScene.getLightingSceneType() == LightingSceneType.LIGHTING_MOOD) { + group.getFixtureRepository().getFixtureMap().forEach((String name, Fixture fixture) -> { + this.setDmxValueForPreset(fixture.getAddress(), ((LightingMood)lightingScene).getDmxValues()); + }); + } else if (lightingScene.getLightingSceneType() == LightingSceneType.COMPOSITION) { + group.getFixtureRepository().getFixtureMap().forEach((String name, Fixture fixture) -> { + this.setDmxValueForPreset(fixture.getAddress(), ((Composition)lightingScene).getDmxValuesForFixture(name)); + }); + } + } + } + + private void setDmxValueForPreset(int address, DmxValues dmxValues) { + System.out.println(String.format("Preset %d, hat einen Blauwert von: %d.", this.group.getId(), Byte.toUnsignedInt(dmxValues.getIntensityBlue()))); + lightController.setPreset(this.id, this.group.getId(), address, dmxValues.getIntensityRed()); + lightController.setPreset(this.id, this.group.getId(), address+1, dmxValues.getIntensityGreen()); + lightController.setPreset(this.id, this.group.getId(), address+2, dmxValues.getIntensityBlue()); + lightController.setPreset(this.id, this.group.getId(), address+3, dmxValues.getIntensityMain()); + lightController.setPreset(this.id, this.group.getId(), address+4, dmxValues.getStropeRate()); + } + + @Override + protected void finalize() { + Preset.numberOfPresets--; + } + + +} diff --git a/src/lightControlSoftware/src/model/preset/PresetRepository.java b/src/lightControlSoftware/src/model/preset/PresetRepository.java new file mode 100644 index 0000000..209d9d6 --- /dev/null +++ b/src/lightControlSoftware/src/model/preset/PresetRepository.java @@ -0,0 +1,40 @@ +package model.preset; + +import java.util.Map; + +import model.generic.PropertyItem; + +import java.util.HashMap; + +public class PresetRepository extends PropertyItem{ + Map<Integer, Preset> presets; + + public PresetRepository() { + this.presets = new HashMap<>(); + } + + public void loadPrests() { + for(int i = 0; i < 6; i++) { + this.addPreset(new Preset()); + } + } + + public void addPreset(Preset preset) { + this.presets.put(preset.getId(), preset); + this.propertySupport.firePropertyChange("presets", null, preset); + } + + public void removePreset(Preset preset) { + preset.deactivate(); + this.propertySupport.firePropertyChange("presets", preset, null); + this.presets.remove(preset.getId()); + } + + public Preset getPreset(int presetId) { + return this.presets.get(presetId); + } + + public Map<Integer, Preset> getPresetsMap() { + return this.presets; + } +} diff --git a/src/lightControlSoftware/src/view/KiwiViewHandler.java b/src/lightControlSoftware/src/view/KiwiViewHandler.java new file mode 100644 index 0000000..ad9987d --- /dev/null +++ b/src/lightControlSoftware/src/view/KiwiViewHandler.java @@ -0,0 +1,41 @@ +package view; + +import javafx.application.Platform; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Stage; +import view.mainScene.MainSceneController; +import viewModel.KiwiViewModelHandler; + +public class KiwiViewHandler{ + + private Stage stage; + private KiwiViewModelHandler viewModelHandler; + + public KiwiViewHandler(Stage stage, KiwiViewModelHandler viewModelHandler) { + this.stage = stage; + this.viewModelHandler = viewModelHandler; + } + + public void start() throws Exception { + FXMLLoader loader = new FXMLLoader(getClass().getResource("mainScene/MainScene.fxml")); + Parent root; + root = loader.load(); + MainSceneController mainSceneController= loader.getController(); + mainSceneController.init(viewModelHandler); + Scene scene = new Scene(root); + scene.getStylesheets().add("style/Style.css"); + this.stage.setMinWidth(800); + this.stage.setMinHeight(625); + this.stage.setTitle("KIWI Illumination Weeny Interface"); + this.stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + this.stage.setScene(scene); + this.stage.show(); + + this.stage.setOnCloseRequest(event -> { + Platform.exit(); + }); + } +} diff --git a/src/lightControlSoftware/src/view/composition/CompositionListItem.java b/src/lightControlSoftware/src/view/composition/CompositionListItem.java new file mode 100644 index 0000000..63506bc --- /dev/null +++ b/src/lightControlSoftware/src/view/composition/CompositionListItem.java @@ -0,0 +1,225 @@ +package view.composition; + +import java.io.IOException; +import java.util.Optional; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import view.composition.editDeletePanel.EditDeleteCompositionController; +import view.elements.ThreeStageSwitchButton; +import viewModel.KiwiViewModelHandler; +import viewModel.composition.CompositionVM; +import viewModel.group.GroupVM; +import viewModel.group.GroupVM.State; + +public class CompositionListItem extends AnchorPane{ + + private CompositionVM compositionVM; + private String compositionName; + private BooleanProperty selected; + private GroupVM groupVM; + + public CompositionListItem(CompositionVM compositionVM, GroupVM groupVM, KiwiViewModelHandler viewModelHandler) { + this.compositionVM = compositionVM; + this.groupVM = groupVM; + this.compositionName = compositionVM.getCompositionName(); + selected = new SimpleBooleanProperty(false); + + setMaxHeight(80.0); + setMinHeight(80.0); + setPrefHeight(80.0); + setMaxWidth(200.0); + setMinWidth(200.0); + setPrefWidth(200.0); + FlowPane.setMargin(this, new Insets(5.0, 5.0, 5.0, 5.0)); + + VBox vBox = new VBox(); + vBox.setAlignment(Pos.CENTER_LEFT); + vBox.setLayoutX(7.0); + vBox.setLayoutY(2.0); + vBox.setMaxWidth(100.0); + AnchorPane.setBottomAnchor(vBox, 1.0); + AnchorPane.setLeftAnchor(vBox, 5.0); + AnchorPane.setRightAnchor(vBox, 5.0); + AnchorPane.setTopAnchor(vBox, 1.0); + + Label lableCompositionName = new Label(compositionName); + lableCompositionName.setAlignment(Pos.TOP_LEFT); + lableCompositionName.setMaxHeight(34.0); + lableCompositionName.setWrapText(true); + lableCompositionName.textProperty().bind(this.compositionVM.compositionNameProperty()); + VBox.setVgrow(lableCompositionName, Priority.ALWAYS); + VBox.setMargin(lableCompositionName, new Insets(5.0, 0.0, 0.0, 0.0)); + + AnchorPane buttonArea = new AnchorPane(); + + VBox vBoxSwitchButton = new VBox(); + vBoxSwitchButton.setAlignment(Pos.CENTER_LEFT); + vBoxSwitchButton.setLayoutX(-1.0); + vBoxSwitchButton.setLayoutY(-1.0); + AnchorPane.setBottomAnchor(vBoxSwitchButton, 0.0); + AnchorPane.setLeftAnchor(vBoxSwitchButton, 5.0); + AnchorPane.setTopAnchor(vBoxSwitchButton, 0.0); + + ThreeStageSwitchButton switchButton = new ThreeStageSwitchButton(); + switchButton.setMaxHeight(20.0); + switchButton.setMinHeight(20.0); + switchButton.setPrefHeight(20.0); + switchButton.setMaxWidth(45.0); + switchButton.setMinWidth(45.0); + switchButton.setPrefWidth(45.0); + + if(this.compositionVM.compositionIsActiveProperty().get()) { + switchButton.buttonStateProperty().set(State.ON); + } + + this.compositionVM.compositionIsActiveProperty().addListener((object, oldValue, newValue) -> { + if(newValue) { + if(switchButton.buttonStateProperty().get() != State.ON) { + //switchButton.buttonStateProperty().set(State.ON); + } + } else { + if(switchButton.buttonStateProperty().get() != State.OFF) { + switchButton.buttonStateProperty().set(State.OFF); + } + } + }); + + + switchButton.buttonStateProperty().addListener((object, oldValue, newValue) -> { + switch(newValue) { + case ON: + this.groupVM.activateComposition(this.compositionVM); + break; + case OFF: + this.groupVM.deactivateComposition(this.compositionVM); + break; + default: + break; + } + }); + + HBox hBox = new HBox(); + hBox.setAlignment(Pos.CENTER_RIGHT); + AnchorPane.setBottomAnchor(hBox, 0.0); + AnchorPane.setRightAnchor(hBox, 5.0); + AnchorPane.setTopAnchor(hBox, 0.0); + + Image icon1 = new Image("images/Edit.png"); + ImageView imageView1 = new ImageView(); + imageView1.setImage(icon1); + imageView1.setFitHeight(25.0); + imageView1.setFitWidth(25.0); + imageView1.setPickOnBounds(true); + imageView1.setPreserveRatio(true); + + + Button buttonEditComposition = new Button(); + buttonEditComposition.setMnemonicParsing(false); + buttonEditComposition.setStyle("-fx-background-color: none"); + buttonEditComposition.setOnAction(event -> { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../composition/editDeletePanel/EditDeleteComposition.fxml")); + Parent editDeleteCompositionPanel = loader.load(); + EditDeleteCompositionController editDeleteCompositionController = loader.getController(); + Scene scene = new Scene(editDeleteCompositionPanel); + Stage compositionDialog = new Stage(); + editDeleteCompositionController.init(compositionDialog, viewModelHandler); + editDeleteCompositionController.setGroup(this.groupVM.getGroupName()); + editDeleteCompositionController.setComposition(compositionName); + compositionDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + compositionDialog.setScene(scene); + compositionDialog.setTitle("Komposition bearbeiten/l�schen"); + compositionDialog.showAndWait(); + editDeleteCompositionController = null; + loader = null; + } catch (IOException e) { + e.printStackTrace(); + } + }); + buttonEditComposition.setGraphic(imageView1); + + Image icon2 = new Image("images/Delete.png"); + ImageView imageView2 = new ImageView(); + imageView2.setImage(icon2); + imageView2.setFitHeight(25.0); + imageView2.setFitWidth(25.0); + imageView2.setPickOnBounds(true); + imageView2.setPreserveRatio(true); + + + Button buttonDeleteComposition = new Button(); + buttonDeleteComposition.setMnemonicParsing(false); + buttonDeleteComposition.setStyle("-fx-background-color: none"); + buttonDeleteComposition.setOnAction(event -> { + Alert alert = new Alert(AlertType.CONFIRMATION); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Komposition l�schen"); + alert.setHeaderText(String.format("Sind Sie sicher, dass Sie die Komposition %s l�schen m�chten", this.getLightingMoodName())); + ButtonType buttonDelete = new ButtonType("Komposition l�schen"); + ButtonType buttonCancel = new ButtonType("Abbrechen", ButtonData.CANCEL_CLOSE); + alert.getButtonTypes().setAll(buttonDelete, buttonCancel); + Optional<ButtonType> result = alert.showAndWait(); + if (result.orElseThrow() == buttonDelete) { + this.groupVM.removeComposition(this.compositionVM); + } + }); + buttonDeleteComposition.setGraphic(imageView2); + + + vBoxSwitchButton.getChildren().add(switchButton); + hBox.getChildren().addAll(buttonEditComposition, buttonDeleteComposition); + buttonArea.getChildren().addAll(vBoxSwitchButton, hBox); + vBox.getChildren().addAll(lableCompositionName, buttonArea); + getChildren().addAll(vBox); + getStyleClass().addAll("list-item", "item-diselected"); + + this.selected.bind(this.compositionVM.selectedProperty()); + + this.selected.addListener((object, oldValue, newValue) -> { + if(newValue) { + this.getStyleClass().add("item-selected"); + this.getStyleClass().remove("item-diselected"); + } else { + this.getStyleClass().add("item-diselected"); + this.getStyleClass().remove("item-selected"); + } + }); + + this.setOnMouseClicked(event -> { + this.compositionVM.setCompositionIsSeleceted(true); + }); + } + + public boolean isSelected() { + return selected.get(); + } + + public BooleanProperty selectedProperty() { + return this.selected; + } + + public String getLightingMoodName() { + return this.compositionName; + } +} diff --git a/src/lightControlSoftware/src/view/composition/createPanel/CreateComposition.fxml b/src/lightControlSoftware/src/view/composition/createPanel/CreateComposition.fxml new file mode 100644 index 0000000..11b6757 --- /dev/null +++ b/src/lightControlSoftware/src/view/composition/createPanel/CreateComposition.fxml @@ -0,0 +1,244 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import java.lang.String?> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ColorPicker?> +<?import javafx.scene.control.ComboBox?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.control.Slider?> +<?import javafx.scene.control.Spinner?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.paint.Color?> +<?import javafx.scene.text.Font?> + +<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="350.0" minWidth="550.0" prefHeight="500.0" prefWidth="800.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.composition.createPanel.CreateCompositionController"> + <center> + <AnchorPane BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="5.0"> + <children> + <BorderPane HBox.hgrow="ALWAYS"> + <center> + <VBox BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER_LEFT" VBox.vgrow="NEVER"> + <children> + <Label minWidth="50.0" text="Farbe" /> + <ColorPicker fx:id="colorPicker"> + <HBox.margin> + <Insets /> + </HBox.margin> + <value> + <Color /> + </value> + </ColorPicker> + </children> + <opaqueInsets> + <Insets /> + </opaqueInsets> + </HBox> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderMainIntensity" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="50.0" text="Dimmer" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets /> + </BorderPane.margin> + </Label> + </left> + <right> + <Spinner fx:id="spinnerMainIntensity" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderStrope" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="50.0" text="Strobe" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets /> + </BorderPane.margin> + </Label> + </left> + <right> + <Spinner fx:id="spinnerStrope" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderRed" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="50.0" text="Rot" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerRed" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderGreen" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="50.0" text="Grün" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerGreen" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderBlue" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="50.0" text="Blau" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerBlue" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + </children> + <BorderPane.margin> + <Insets top="10.0" /> + </BorderPane.margin> + </VBox> + </center> + <HBox.margin> + <Insets left="20.0" /> + </HBox.margin> + </BorderPane> + </children> + </HBox> + </children> + </AnchorPane> + </center> + <bottom> + <HBox alignment="CENTER" BorderPane.alignment="CENTER"> + <children> + <Button fx:id="buttonSave" mnemonicParsing="false" onAction="#saveComposition" text="Komposition erstellen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + <Button fx:id="buttonCancel" mnemonicParsing="false" onAction="#cancel" text="Abbrechen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + </children> + </HBox> + </bottom> + <left> + <VBox BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER_LEFT"> + <children> + <Label minWidth="75.0" prefWidth="75.0" text="Gruppe" HBox.hgrow="NEVER"> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </Label> + <ComboBox fx:id="comboBoxGroup" minWidth="150.0" onAction="#selectGroup" /> + </children> + <VBox.margin> + <Insets top="10.0" /> + </VBox.margin> + </HBox> + <HBox alignment="CENTER_LEFT"> + <VBox.margin> + <Insets top="10.0" /> + </VBox.margin> + <children> + <HBox alignment="CENTER_LEFT"> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Bezeichnung*" HBox.hgrow="NEVER"> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin></Label> + <TextField fx:id="textfieldName" /> + </children> + <HBox.margin> + <Insets right="10.0" /> + </HBox.margin> + </HBox> + </children> + </HBox> + <HBox alignment="CENTER_LEFT"> + <children> + <Label minWidth="75.0" prefWidth="75.0" text="Lampe" HBox.hgrow="NEVER"> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </Label> + <ComboBox fx:id="comboBoxFixture" minWidth="150.0" onAction="#selectFixture" /> + </children> + <VBox.margin> + <Insets top="10.0" /> + </VBox.margin> + </HBox> + <AnchorPane prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS"> + <children> + <AnchorPane prefHeight="200.0" prefWidth="200.0" styleClass="section" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="10.0"> + <children> + <ScrollPane fitToHeight="true" fitToWidth="true" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <content> + <ListView fx:id="lightMoodList" prefHeight="200.0" prefWidth="200.0"> + <styleClass> + <String fx:value="list-cell" /> + <String fx:value="white-background" /> + </styleClass> + </ListView> + </content> + </ScrollPane> + </children> + <padding> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </padding> + </AnchorPane> + <Label styleClass="section-headline" text="Lichtstimmung" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="0.0"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Label> + </children> + <VBox.margin> + <Insets right="5.0" top="10.0" /> + </VBox.margin> + </AnchorPane> + </children> + <BorderPane.margin> + <Insets left="10.0" /> + </BorderPane.margin> + </VBox> + </left> +</BorderPane> diff --git a/src/lightControlSoftware/src/view/composition/createPanel/CreateCompositionController.java b/src/lightControlSoftware/src/view/composition/createPanel/CreateCompositionController.java new file mode 100644 index 0000000..16c50b7 --- /dev/null +++ b/src/lightControlSoftware/src/view/composition/createPanel/CreateCompositionController.java @@ -0,0 +1,333 @@ +package view.composition.createPanel; + +import javafx.fxml.FXML; + +import javafx.scene.control.ColorPicker; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.TextField; +import javafx.scene.control.ToggleGroup; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.image.Image; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.scene.control.Slider; +import javafx.scene.control.ListView; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.layout.BorderPane; +import javafx.scene.paint.Color; +import javafx.stage.Stage; +import model.lightingScene.composition.Composition; +import viewModel.KiwiViewModelHandler; +import viewModel.composition.CompositionVM; +import viewModel.fixture.DmxValuesVM; +import viewModel.fixture.FixtureVM; +import viewModel.group.GroupRepositoryVM; +import viewModel.group.GroupVM; +import viewModel.lightingMood.LightingMoodVM; + +public class CreateCompositionController { + @FXML + private BorderPane root; + @FXML + private ColorPicker colorPicker; + @FXML + private Slider sliderMainIntensity; + @FXML + private Spinner<Integer> spinnerMainIntensity; + @FXML + private Slider sliderStrope; + @FXML + private Spinner<Integer> spinnerStrope; + @FXML + private Slider sliderRed; + @FXML + private Spinner<Integer> spinnerRed; + @FXML + private Slider sliderGreen; + @FXML + private Spinner<Integer> spinnerGreen; + @FXML + private Slider sliderBlue; + @FXML + private Spinner<Integer> spinnerBlue; + @FXML + private Button buttonSave; + @FXML + private Button buttonCancel; + @FXML + private ComboBox<String> comboBoxGroup; + @FXML + private TextField textfieldName; + @FXML + private ComboBox<String> comboBoxFixture; + @FXML + private ListView<RadioButton> lightMoodList; + + private Stage stage; + private GroupVM groupVM; + private FixtureVM fixtureVM; + private GroupRepositoryVM groupRepositoryVM; + private ToggleGroup toggelGroup = new ToggleGroup(); + private ObservableList<String> groupNames = FXCollections.observableArrayList(); + private ObservableList<String> fixtureNames = FXCollections.observableArrayList(); + private ObservableList<RadioButton> lightingMoods = FXCollections.observableArrayList(); + + + public void init(Stage stage, KiwiViewModelHandler viewModelHandler) { + this.stage = stage; + this.groupRepositoryVM = viewModelHandler.getGroupRepositoryVM(); + this.spinnerMainIntensity.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.spinnerStrope.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.spinnerRed.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.spinnerGreen.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.spinnerBlue.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.lightMoodList.setItems(lightingMoods); + this.addListener(); + this.loadGroups(); + this.updateLightingMoodList(); + this.loadFixtures(); + } + + // Event Listener on Button[#buttonSave].onAction + @FXML + public void saveComposition(ActionEvent event) { + if(this.isDataValid()) { + + try { + CompositionVM compositionVM = new CompositionVM(new Composition(this.textfieldName.getText())); + this.fixtureNames.forEach(fixtureName -> { + FixtureVM fixtureVM = this.groupVM.getFixtureRepositoryVM().getFixtureVM(fixtureName); + compositionVM.setDmxValuesForFixture(fixtureVM.getFixtureName(), fixtureVM.getDmxValuesVM()); + }); + + this.groupRepositoryVM + .getGroupVM(this.comboBoxGroup.getValue()) + .addComposition(compositionVM); + this.stage.close(); + } catch (IllegalArgumentException e) { + Alert alert = new Alert(AlertType.ERROR); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Fehler"); + alert.setHeaderText("Fehler beim Erstellen einer Lichtstimmung."); + alert.setContentText(e.getMessage()); + alert.showAndWait(); + } + } + } + // Event Listener on Button[#buttonCancel].onAction + @FXML + public void cancel(ActionEvent event) { + this.stage.close(); + } + // Event Listener on ComboBox[#comboBoxGroup].onAction + @FXML + public void selectGroup(ActionEvent event) { + if(!(this.comboBoxGroup.getValue() == null)) { + this.groupVM = groupRepositoryVM.getGroupVM(this.comboBoxGroup.getValue()); + this.updateLightingMoodList(); + this.loadFixtures(); + } + } + // Event Listener on ComboBox[#comboBoxFixture].onAction + @FXML + public void selectFixture(ActionEvent event) { + if(!(this.comboBoxFixture.getValue() == null)) { + this.fixtureVM = this.groupVM.getFixtureRepositoryVM().getFixtureVM(this.comboBoxFixture.getValue()); + this.lightMoodList.getItems().forEach(radioButton -> { + if(radioButton.getText().equals(fixtureVM.getLightingMoodName())) { + this.updateSliderDmxValues(this.fixtureVM.getDmxValuesVM()); + radioButton.selectedProperty().set(true); + } + }); + } + } + + private void addListener() { + + this.addSpinnerListener(this.spinnerMainIntensity, this.sliderMainIntensity); + this.addSpinnerListener(this.spinnerStrope, this.sliderStrope); + this.addSpinnerListener(this.spinnerRed, this.sliderRed); + this.addSpinnerListener(this.spinnerGreen, this.sliderGreen); + this.addSpinnerListener(this.spinnerBlue, this.sliderBlue); + + this.addSliderListener(this.spinnerMainIntensity, this.sliderMainIntensity); + this.addSliderListener(this.spinnerStrope, this.sliderStrope); + this.addSliderListener(this.spinnerRed, this.sliderRed); + this.addSliderListener(this.spinnerGreen, this.sliderGreen); + this.addSliderListener(this.spinnerBlue, this.sliderBlue); + + this.colorPicker.valueProperty().addListener((onject, oldValue, newValue) -> { + this.sliderRed.setValue(newValue.getRed()*255); + this.sliderGreen.setValue(newValue.getGreen()*255); + this.sliderBlue.setValue(newValue.getBlue()*255); + }); + + this.sliderRed.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + (double)newValue/255, + this.sliderGreen.getValue()/255, + this.sliderBlue.getValue()/255, 1 + )); + }); + + this.sliderGreen.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + this.sliderRed.getValue()/255, + (double)newValue/255, + this.sliderBlue.getValue()/255, 1 + )); + }); + + this.sliderBlue.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + this.sliderRed.getValue()/255, + this.sliderGreen.getValue()/255, + (double)newValue/255, 1 + )); + }); + } + + private void addSpinnerListener (Spinner<Integer> spinner, Slider slider) { + spinner.getValueFactory().valueProperty().addListener((object, oldValue, newValue) -> { + slider.valueProperty().set(newValue.doubleValue()); + }); + } + + private void addSliderListener (Spinner<Integer> spinner, Slider slider) { + slider.valueProperty().addListener((object, oldValue, newValue) -> { + if(this.toggelGroup.getSelectedToggle() != null + && this.fixtureVM.getLightingMoodName().equals(((RadioButton)toggelGroup.getSelectedToggle()).getText())) { + this.updateFixtureDmxValues(); + if(!this.toggelGroup.getToggles().get(0).selectedProperty().get()) { + this.toggelGroup.getToggles().get(0).selectedProperty().set(true); + } + } + spinner.getValueFactory().setValue(newValue.intValue()); + }); + } + + private void loadGroups() { + this.groupNames.clear(); + this.groupRepositoryVM.groupsListProperty().forEach(groupVM -> { + this.groupNames.add(groupVM.getGroupName()); + }); + this.groupVM = this.groupRepositoryVM.getGroupVM(this.groupNames.get(0)); + this.comboBoxGroup.setItems(this.groupNames); + this.comboBoxGroup.setValue(this.groupNames.get(0)); + } + + private void loadFixtures() { + this.fixtureNames.clear(); + this.groupVM.getFixtureRepositoryVM().fixtureListProperty().forEach(fixtureVM -> { + this.fixtureNames.add(fixtureVM.getFixtureName()); + }); + this.fixtureVM = this.groupVM.getFixtureRepositoryVM().getFixtureVM(this.fixtureNames.get(0)); + this.comboBoxFixture.setItems(this.fixtureNames); + this.comboBoxFixture.setValue(this.fixtureNames.get(0)); + this.lightMoodList.getItems().forEach(radioButton -> { + if(radioButton.getText().equals(this.fixtureVM.getLightingMoodName())) { + this.updateSliderDmxValues(this.fixtureVM.getDmxValuesVM()); + radioButton.selectedProperty().set(true); + } + }); + + } + + private void updateLightingMoodList() { + this.lightingMoods.clear(); + this.toggelGroup = new ToggleGroup(); + this.toggelGroup.selectedToggleProperty().addListener((object, oldValue, newValue) -> { + if(this.fixtureVM != null) { + RadioButton radioButton = (RadioButton) newValue; + if(!radioButton.getText().equals("Benutzerdefeniert")) { + LightingMoodVM lightingMoodVM = this.groupVM.getLightingMoodRepositoryVM().getLightingMood(radioButton.getText()); + this.updateSliderDmxValues(lightingMoodVM.getDmxValuesVM()); + this.updateFixtureDmxValues(lightingMoodVM.getDmxValuesVM()); + this.fixtureVM.setLightingMoodName(radioButton.getText()); + } else { + this.fixtureVM.setLightingMoodName(radioButton.getText()); + } + } + }); + + RadioButton userSpezified = new RadioButton("Benutzerdefeniert"); + userSpezified.setToggleGroup(toggelGroup); + this.lightingMoods.add(userSpezified); + this.groupVM.getLightingMoodRepositoryVM().lightingMoodListProperty().forEach(lightingMood -> { + RadioButton lightingMoodRadioButton = new RadioButton(lightingMood.getLightingMoodName()); + lightingMoodRadioButton.setToggleGroup(toggelGroup); + this.lightingMoods.add(lightingMoodRadioButton); + }); + } + + private void updateSliderDmxValues(DmxValuesVM dmxValuesVM) { + this.sliderMainIntensity.valueProperty().set(dmxValuesVM.getMainIntensity()); + this.sliderStrope.valueProperty().set(dmxValuesVM.getStrope()); + this.sliderRed.valueProperty().set(dmxValuesVM.getRed()); + this.sliderGreen.valueProperty().set(dmxValuesVM.getGreen()); + this.sliderBlue.valueProperty().set(dmxValuesVM.getBlue()); + } + + private void updateFixtureDmxValues() { + this.fixtureVM.getDmxValuesVM().setMainIntensity(this.sliderMainIntensity.valueProperty().getValue().intValue()); + this.fixtureVM.getDmxValuesVM().setStrope(this.sliderStrope.valueProperty().getValue().intValue()); + this.fixtureVM.getDmxValuesVM().setRed(this.sliderRed.valueProperty().getValue().intValue()); + this.fixtureVM.getDmxValuesVM().setGreen(this.sliderGreen.valueProperty().getValue().intValue()); + this.fixtureVM.getDmxValuesVM().setBlue(this.sliderBlue.valueProperty().getValue().intValue()); + } + + private void updateFixtureDmxValues(DmxValuesVM dmxValuesVM) { + this.fixtureVM.getDmxValuesVM().setMainIntensity(dmxValuesVM.getMainIntensity()); + this.fixtureVM.getDmxValuesVM().setStrope(dmxValuesVM.getStrope()); + this.fixtureVM.getDmxValuesVM().setRed(dmxValuesVM.getRed()); + this.fixtureVM.getDmxValuesVM().setGreen(dmxValuesVM.getGreen()); + this.fixtureVM.getDmxValuesVM().setBlue(dmxValuesVM.getBlue()); + } + + private boolean isDataValid() { + boolean isValid = false; + String headerText = ""; + String contentText = ""; + + if(this.textfieldName.getText().isEmpty()) { + headerText = "Fehlerhafter Name."; + contentText = "Das Namensfeld darf nicht leer sein."; + } else { + isValid = true; + } + + if(!isValid) { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.showAndWait(); + } + + return isValid; + } + + public void setGroup(String name) { + this.comboBoxGroup.valueProperty().set(name); + this.groupVM = groupRepositoryVM.getGroupVM(name); + this.updateLightingMoodList(); + this.loadFixtures(); + } +} diff --git a/src/lightControlSoftware/src/view/composition/editDeletePanel/EditDeleteComposition.fxml b/src/lightControlSoftware/src/view/composition/editDeletePanel/EditDeleteComposition.fxml new file mode 100644 index 0000000..195f1ed --- /dev/null +++ b/src/lightControlSoftware/src/view/composition/editDeletePanel/EditDeleteComposition.fxml @@ -0,0 +1,257 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import java.lang.String?> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ColorPicker?> +<?import javafx.scene.control.ComboBox?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.control.Slider?> +<?import javafx.scene.control.Spinner?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.paint.Color?> +<?import javafx.scene.text.Font?> + +<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="350.0" minWidth="550.0" prefHeight="500.0" prefWidth="800.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.composition.editDeletePanel.EditDeleteCompositionController"> + <center> + <AnchorPane BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="5.0"> + <children> + <BorderPane HBox.hgrow="ALWAYS"> + <center> + <VBox BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER_LEFT" VBox.vgrow="NEVER"> + <children> + <Label minWidth="50.0" text="Farbe" /> + <ColorPicker fx:id="colorPicker"> + <HBox.margin> + <Insets /> + </HBox.margin> + <value> + <Color /> + </value> + </ColorPicker> + </children> + <opaqueInsets> + <Insets /> + </opaqueInsets> + </HBox> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderMainIntensity" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="50.0" text="Dimmer" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets /> + </BorderPane.margin> + </Label> + </left> + <right> + <Spinner fx:id="spinnerMainIntensity" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderStrope" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="50.0" text="Strobe" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets /> + </BorderPane.margin> + </Label> + </left> + <right> + <Spinner fx:id="spinnerStrope" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderRed" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="50.0" text="Rot" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerRed" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderGreen" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="50.0" text="Grün" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerGreen" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderBlue" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="50.0" text="Blau" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerBlue" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + </children> + <BorderPane.margin> + <Insets top="10.0" /> + </BorderPane.margin> + </VBox> + </center> + <HBox.margin> + <Insets left="20.0" /> + </HBox.margin> + </BorderPane> + </children> + </HBox> + </children> + </AnchorPane> + </center> + <bottom> + <HBox alignment="CENTER" BorderPane.alignment="CENTER"> + <children> + <Button fx:id="buttonSave" mnemonicParsing="false" onAction="#saveComposition" text="Speichern"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + <Button fx:id="buttonCancel" mnemonicParsing="false" onAction="#cancel" text="Abbrechen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + </children> + </HBox> + </bottom> + <left> + <VBox BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER_LEFT"> + <children> + <Label minWidth="75.0" prefWidth="75.0" text="Gruppe" HBox.hgrow="NEVER"> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </Label> + <ComboBox fx:id="comboBoxGroup" minWidth="150.0" onAction="#selectGroup" /> + </children> + <VBox.margin> + <Insets top="10.0" /> + </VBox.margin> + </HBox> + <HBox alignment="CENTER_LEFT"> + <children> + <Label minWidth="75.0" prefWidth="75.0" text="Komposition" HBox.hgrow="NEVER"> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </Label> + <ComboBox fx:id="comboBoxComposition" minWidth="150.0" onAction="#selectComposition" /> + </children> + <VBox.margin> + <Insets top="10.0" /> + </VBox.margin> + </HBox> + <HBox alignment="CENTER_LEFT"> + <VBox.margin> + <Insets top="10.0" /> + </VBox.margin> + <children> + <HBox alignment="CENTER_LEFT"> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Bezeichnung*" HBox.hgrow="NEVER"> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin></Label> + <TextField fx:id="textfieldName" /> + </children> + <HBox.margin> + <Insets right="10.0" /> + </HBox.margin> + </HBox> + </children> + </HBox> + <HBox alignment="CENTER_LEFT"> + <children> + <Label minWidth="75.0" prefWidth="75.0" text="Lampe" HBox.hgrow="NEVER"> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </Label> + <ComboBox fx:id="comboBoxFixture" minWidth="150.0" onAction="#selectFixture" /> + </children> + <VBox.margin> + <Insets top="10.0" /> + </VBox.margin> + </HBox> + <AnchorPane prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS"> + <children> + <AnchorPane prefHeight="200.0" prefWidth="200.0" styleClass="section" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="10.0"> + <children> + <ScrollPane fitToHeight="true" fitToWidth="true" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <content> + <ListView fx:id="lightMoodList" prefHeight="200.0" prefWidth="200.0"> + <styleClass> + <String fx:value="list-cell" /> + <String fx:value="white-background" /> + </styleClass> + </ListView> + </content> + </ScrollPane> + </children> + <padding> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </padding> + </AnchorPane> + <Label styleClass="section-headline" text="Lichtstimmung" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="0.0"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Label> + </children> + <VBox.margin> + <Insets right="5.0" top="10.0" /> + </VBox.margin> + </AnchorPane> + </children> + <BorderPane.margin> + <Insets left="10.0" /> + </BorderPane.margin> + </VBox> + </left> +</BorderPane> diff --git a/src/lightControlSoftware/src/view/composition/editDeletePanel/EditDeleteCompositionController.java b/src/lightControlSoftware/src/view/composition/editDeletePanel/EditDeleteCompositionController.java new file mode 100644 index 0000000..cb438f1 --- /dev/null +++ b/src/lightControlSoftware/src/view/composition/editDeletePanel/EditDeleteCompositionController.java @@ -0,0 +1,394 @@ +package view.composition.editDeletePanel; + +import javafx.fxml.FXML; + +import javafx.scene.control.ColorPicker; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; + +import javafx.scene.control.TextField; +import javafx.scene.control.ToggleGroup; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.image.Image; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; + +import javafx.scene.control.Slider; + +import javafx.scene.control.ListView; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ComboBox; + +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.layout.BorderPane; +import javafx.scene.paint.Color; +import javafx.stage.Stage; +import viewModel.KiwiViewModelHandler; +import viewModel.composition.CompositionRepositoryVM; +import viewModel.composition.CompositionVM; +import viewModel.fixture.DmxValuesVM; +import viewModel.fixture.FixtureVM; +import viewModel.group.GroupRepositoryVM; +import viewModel.group.GroupVM; +import viewModel.lightingMood.LightingMoodVM; + +public class EditDeleteCompositionController { + @FXML + private BorderPane root; + @FXML + private ColorPicker colorPicker; + @FXML + private Slider sliderMainIntensity; + @FXML + private Spinner<Integer> spinnerMainIntensity; + @FXML + private Slider sliderStrope; + @FXML + private Spinner<Integer> spinnerStrope; + @FXML + private Slider sliderRed; + @FXML + private Spinner<Integer> spinnerRed; + @FXML + private Slider sliderGreen; + @FXML + private Spinner<Integer> spinnerGreen; + @FXML + private Slider sliderBlue; + @FXML + private Spinner<Integer> spinnerBlue; + @FXML + private Button buttonSave; + @FXML + private Button buttonCancel; + @FXML + private ComboBox<String> comboBoxGroup; + @FXML + private ComboBox<String> comboBoxComposition; + @FXML + private TextField textfieldName; + @FXML + private ComboBox<String> comboBoxFixture; + @FXML + private ListView<RadioButton> lightMoodList; + + private Stage stage; + private GroupVM groupVM; + private FixtureVM fixtureVM; + private CompositionVM compositionVM; + private GroupRepositoryVM groupRepositoryVM; + private KiwiViewModelHandler viewModelHandler; + private ToggleGroup toggelGroup = new ToggleGroup(); + private CompositionRepositoryVM compositionRepositoryVM; + private ObservableList<String> groupNames = FXCollections.observableArrayList(); + private ObservableList<String> fixtureNames = FXCollections.observableArrayList(); + private ObservableList<String> compositionNames = FXCollections.observableArrayList(); + private ObservableList<RadioButton> lightingMoods = FXCollections.observableArrayList(); + + + public void init(Stage stage, KiwiViewModelHandler viewModelHandler) { + this.stage = stage; + this.viewModelHandler = viewModelHandler; + this.groupRepositoryVM = this.viewModelHandler.getGroupRepositoryVM(); + this.spinnerMainIntensity.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.spinnerStrope.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.spinnerRed.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.spinnerGreen.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.spinnerBlue.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.lightMoodList.setItems(lightingMoods); + this.addListener(); + this.loadGroups(); + this.loadCompositions(); + this.updateLightingMoodList(); + this.loadFixtures(); + this.updateFixtures(); + + } + + + // Event Listener on Button[#buttonSave].onAction + @FXML + public void saveComposition(ActionEvent event) { + if(this.isDataValid()) { + try { + this.compositionVM.setCompositionName(this.textfieldName.getText()); + this.fixtureNames.forEach(fixtureName -> { + FixtureVM fixtureVM = this.groupVM.getFixtureRepositoryVM().getFixtureVM(fixtureName); + this.compositionVM.setDmxValuesForFixture(fixtureVM.getFixtureName(), fixtureVM.getDmxValuesVM()); + }); + this.stage.close(); + } catch (IllegalArgumentException e) { + Alert alert = new Alert(AlertType.ERROR); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Fehler"); + alert.setHeaderText("Fehler beim Erstellen einer Lichtstimmung."); + alert.setContentText(e.getMessage()); + alert.showAndWait(); + } + } + } + // Event Listener on Button[#buttonCancel].onAction + @FXML + public void cancel(ActionEvent event) { + this.stage.close(); + } + // Event Listener on ComboBox[#comboBoxGroup].onAction + @FXML + public void selectGroup(ActionEvent event) { + if(!(this.comboBoxGroup.getValue() == null)) { + this.groupVM = groupRepositoryVM.getGroupVM(this.comboBoxGroup.getValue()); + this.loadCompositions(); + this.updateLightingMoodList(); + this.updateFixtures(); + this.loadFixtures(); + } + } + // Event Listener on ComboBox[#comboBoxComposition].onAction + @FXML + public void selectComposition(ActionEvent event) { + this.compositionVM = this.groupVM.getCompositionRepositoryVM().getComposition(this.comboBoxComposition.getValue()); + this.textfieldName.textProperty().set(this.compositionVM.getCompositionName()); + this.updateFixtures(); + this.updateSliderDmxValues(this.fixtureVM.getDmxValuesVM()); + } + // Event Listener on ComboBox[#comboBoxFixture].onAction + @FXML + public void selectFixture(ActionEvent event) { + if(!(this.comboBoxFixture.getValue() == null)) { + this.fixtureVM = this.groupVM.getFixtureRepositoryVM().getFixtureVM(this.comboBoxFixture.getValue()); + this.lightMoodList.getItems().forEach(radioButton -> { + if(radioButton.getText().equals(fixtureVM.getLightingMoodName())) { + this.updateSliderDmxValues(this.fixtureVM.getDmxValuesVM()); + radioButton.selectedProperty().set(true); + } + }); + } + } + + private void addListener() { + + this.addSpinnerListener(this.spinnerMainIntensity, this.sliderMainIntensity); + this.addSpinnerListener(this.spinnerStrope, this.sliderStrope); + this.addSpinnerListener(this.spinnerRed, this.sliderRed); + this.addSpinnerListener(this.spinnerGreen, this.sliderGreen); + this.addSpinnerListener(this.spinnerBlue, this.sliderBlue); + + this.addSliderListener(this.spinnerMainIntensity, this.sliderMainIntensity); + this.addSliderListener(this.spinnerStrope, this.sliderStrope); + this.addSliderListener(this.spinnerRed, this.sliderRed); + this.addSliderListener(this.spinnerGreen, this.sliderGreen); + this.addSliderListener(this.spinnerBlue, this.sliderBlue); + + this.colorPicker.valueProperty().addListener((onject, oldValue, newValue) -> { + this.sliderRed.setValue(newValue.getRed()*255); + this.sliderGreen.setValue(newValue.getGreen()*255); + this.sliderBlue.setValue(newValue.getBlue()*255); + }); + + this.sliderRed.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + (double)newValue/255, + this.sliderGreen.getValue()/255, + this.sliderBlue.getValue()/255, 1 + )); + }); + + this.sliderGreen.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + this.sliderRed.getValue()/255, + (double)newValue/255, + this.sliderBlue.getValue()/255, 1 + )); + }); + + this.sliderBlue.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + this.sliderRed.getValue()/255, + this.sliderGreen.getValue()/255, + (double)newValue/255, 1 + )); + }); + } + + private void addSpinnerListener (Spinner<Integer> spinner, Slider slider) { + spinner.getValueFactory().valueProperty().addListener((object, oldValue, newValue) -> { + slider.valueProperty().set(newValue.doubleValue()); + }); + } + + private void addSliderListener (Spinner<Integer> spinner, Slider slider) { + slider.valueProperty().addListener((object, oldValue, newValue) -> { + if(this.toggelGroup.getSelectedToggle() != null + && this.fixtureVM.getLightingMoodName().equals(((RadioButton)toggelGroup.getSelectedToggle()).getText())) { + this.updateFixtureDmxValues(); + if(!this.toggelGroup.getToggles().get(0).selectedProperty().get()) { + this.toggelGroup.getToggles().get(0).selectedProperty().set(true); + } + } + spinner.getValueFactory().setValue(newValue.intValue()); + }); + } + + private void loadGroups() { + this.groupNames.clear(); + this.groupRepositoryVM.groupsListProperty().forEach(groupVM -> { + this.groupNames.add(groupVM.getGroupName()); + }); + this.groupVM = this.groupRepositoryVM.getGroupVM(this.groupNames.get(0)); + this.comboBoxGroup.setItems(this.groupNames); + this.comboBoxGroup.setValue(this.groupNames.get(0)); + this.compositionRepositoryVM = this.groupVM.getCompositionRepositoryVM(); + } + + private void loadCompositions() { + this.compositionNames.clear(); + this.compositionRepositoryVM = this.groupVM.getCompositionRepositoryVM(); + this.compositionRepositoryVM.compositionListProperty().forEach(compositionVM -> { + this.compositionNames.add(compositionVM.getCompositionName()); + }); + if(this.compositionNames.size() > 0) { + this.compositionVM = this.compositionRepositoryVM.getComposition(this.compositionNames.get(0)); + this.comboBoxComposition.setItems(this.compositionNames); + this.comboBoxComposition.setValue(this.compositionNames.get(0)); + } + } + + private void loadFixtures() { + this.fixtureNames.clear(); + this.groupVM.getFixtureRepositoryVM().fixtureListProperty().forEach(fixtureVM -> { + this.fixtureNames.add(fixtureVM.getFixtureName()); + }); + this.fixtureVM = this.groupVM.getFixtureRepositoryVM().getFixtureVM(this.fixtureNames.get(0)); + this.lightMoodList.getItems().forEach(radioButton -> { + if(radioButton.getText().equals(this.fixtureVM.getLightingMoodName())) { + this.updateSliderDmxValues(this.fixtureVM.getDmxValuesVM()); + radioButton.selectedProperty().set(true); + } + }); + + this.comboBoxFixture.setItems(this.fixtureNames); + this.comboBoxFixture.setValue(this.fixtureNames.get(0)); + + } + + private void updateLightingMoodList() { + this.lightingMoods.clear(); + this.toggelGroup = new ToggleGroup(); + this.toggelGroup.selectedToggleProperty().addListener((object, oldValue, newValue) -> { + if(this.fixtureVM != null) { + RadioButton radioButton = (RadioButton) newValue; + if(!radioButton.getText().equals("Benutzerdefeniert")) { + LightingMoodVM lightingMoodVM = this.groupVM.getLightingMoodRepositoryVM().getLightingMood(radioButton.getText()); + this.updateSliderDmxValues(lightingMoodVM.getDmxValuesVM()); + this.updateFixtureDmxValues(lightingMoodVM.getDmxValuesVM()); + this.fixtureVM.setLightingMoodName(radioButton.getText()); + } else { + this.fixtureVM.setLightingMoodName(radioButton.getText()); + } + } + }); + + RadioButton userSpezified = new RadioButton("Benutzerdefeniert"); + userSpezified.setToggleGroup(toggelGroup); + this.lightingMoods.add(userSpezified); + this.groupVM.getLightingMoodRepositoryVM().lightingMoodListProperty().forEach(lightingMood -> { + RadioButton lightingMoodRadioButton = new RadioButton(lightingMood.getLightingMoodName()); + lightingMoodRadioButton.setToggleGroup(toggelGroup); + this.lightingMoods.add(lightingMoodRadioButton); + }); + } + + private void updateSliderDmxValues(DmxValuesVM dmxValuesVM) { + this.sliderMainIntensity.valueProperty().set(dmxValuesVM.getMainIntensity()); + this.sliderStrope.valueProperty().set(dmxValuesVM.getStrope()); + this.sliderRed.valueProperty().set(dmxValuesVM.getRed()); + this.sliderGreen.valueProperty().set(dmxValuesVM.getGreen()); + this.sliderBlue.valueProperty().set(dmxValuesVM.getBlue()); + } + + private void updateFixtureDmxValues() { + this.fixtureVM.getDmxValuesVM().setMainIntensity(this.sliderMainIntensity.valueProperty().getValue().intValue()); + this.fixtureVM.getDmxValuesVM().setStrope(this.sliderStrope.valueProperty().getValue().intValue()); + this.fixtureVM.getDmxValuesVM().setRed(this.sliderRed.valueProperty().getValue().intValue()); + this.fixtureVM.getDmxValuesVM().setGreen(this.sliderGreen.valueProperty().getValue().intValue()); + this.fixtureVM.getDmxValuesVM().setBlue(this.sliderBlue.valueProperty().getValue().intValue()); + } + + private void updateFixtureDmxValues(DmxValuesVM dmxValuesVM) { + this.fixtureVM.getDmxValuesVM().setMainIntensity(dmxValuesVM.getMainIntensity()); + this.fixtureVM.getDmxValuesVM().setStrope(dmxValuesVM.getStrope()); + this.fixtureVM.getDmxValuesVM().setRed(dmxValuesVM.getRed()); + this.fixtureVM.getDmxValuesVM().setGreen(dmxValuesVM.getGreen()); + this.fixtureVM.getDmxValuesVM().setBlue(dmxValuesVM.getBlue()); + } + + private void updateFixtureDmxValues(FixtureVM fixtureVM, DmxValuesVM dmxValuesVM) { + fixtureVM.getDmxValuesVM().setMainIntensity(dmxValuesVM.getMainIntensity()); + fixtureVM.getDmxValuesVM().setStrope(dmxValuesVM.getStrope()); + fixtureVM.getDmxValuesVM().setRed(dmxValuesVM.getRed()); + fixtureVM.getDmxValuesVM().setGreen(dmxValuesVM.getGreen()); + fixtureVM.getDmxValuesVM().setBlue(dmxValuesVM.getBlue()); + } + + private boolean isDataValid() { + boolean isValid = false; + String headerText = ""; + String contentText = ""; + + if(this.textfieldName.getText().isEmpty()) { + headerText = "Fehlerhafter Name."; + contentText = "Das Namensfeld darf nicht leer sein."; + } else { + isValid = true; + } + + if(!isValid) { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.showAndWait(); + } + + return isValid; + } + + private void updateFixtures() { + this.groupVM.getFixtureRepositoryVM().fixtureListProperty().forEach(fixtureVM -> { + if(this.compositionVM != null && this.compositionVM.getDmxValuesForFixture(fixtureVM.getFixtureName()) != null) { + this.updateFixtureDmxValues(fixtureVM, this.compositionVM.getDmxValuesForFixture(fixtureVM.getFixtureName())); + fixtureVM.setLightingMoodName("Benutzerdefeniert"); + } + }); + } + + public void setGroup(String name) { + this.comboBoxGroup.valueProperty().set(name); + this.groupVM = groupRepositoryVM.getGroupVM(name); + this.updateLightingMoodList(); + this.loadFixtures(); + } + + public void setComposition(String name) { + this.comboBoxComposition.valueProperty().set(name); + this.compositionVM = this.groupVM.getCompositionRepositoryVM().getComposition(this.comboBoxComposition.getValue()); + this.textfieldName.textProperty().set(this.compositionVM.getCompositionName()); + this.updateFixtures(); + this.updateSliderDmxValues(this.fixtureVM.getDmxValuesVM()); + } + +} diff --git a/src/lightControlSoftware/src/view/composition/mainPanel/CompositionMainPanel.fxml b/src/lightControlSoftware/src/view/composition/mainPanel/CompositionMainPanel.fxml new file mode 100644 index 0000000..7532a43 --- /dev/null +++ b/src/lightControlSoftware/src/view/composition/mainPanel/CompositionMainPanel.fxml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.FlowPane?> +<?import javafx.scene.text.Font?> + +<AnchorPane prefHeight="350.0" prefWidth="880.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.composition.mainPanel.CompositionMainPanelController"> + <children> + <AnchorPane layoutX="5.0" layoutY="7.0" styleClass="section" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0"> + <children> + <BorderPane AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <center> + <ScrollPane fitToHeight="true" fitToWidth="true"> + <content> + <FlowPane fx:id="flowPaneCompositionList" /> + </content> + </ScrollPane> + </center> + <top> + <Button fx:id="buttonCreateNewCompositoin" mnemonicParsing="false" onAction="#createComposition" text="Neue Komposition erstellen" BorderPane.alignment="CENTER_LEFT"> + <BorderPane.margin> + <Insets bottom="5.0" left="5.0" top="5.0" /> + </BorderPane.margin> + </Button> + </top> + </BorderPane> + </children> + <padding> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </padding> + </AnchorPane> + <Label layoutX="20.0" styleClass="section-headline" text="Kompositionen" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="0.0"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Label> + </children> +</AnchorPane> diff --git a/src/lightControlSoftware/src/view/composition/mainPanel/CompositionMainPanelController.java b/src/lightControlSoftware/src/view/composition/mainPanel/CompositionMainPanelController.java new file mode 100644 index 0000000..4e292b0 --- /dev/null +++ b/src/lightControlSoftware/src/view/composition/mainPanel/CompositionMainPanelController.java @@ -0,0 +1,95 @@ +package view.composition.mainPanel; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.image.Image; + +import java.io.IOException; + +import javafx.collections.ListChangeListener; +import javafx.event.ActionEvent; + +import javafx.scene.layout.FlowPane; +import javafx.stage.Stage; +import view.composition.CompositionListItem; +import view.composition.createPanel.CreateCompositionController; +import viewModel.KiwiViewModelHandler; +import viewModel.composition.CompositionRepositoryVM; +import viewModel.composition.CompositionVM; +import viewModel.group.GroupVM; + +public class CompositionMainPanelController { + @FXML + private FlowPane flowPaneCompositionList; + @FXML + private Button buttonCreateNewCompositoin; + + private KiwiViewModelHandler viewModelHandler; + private CompositionRepositoryVM compositionRepositoryVM; + private GroupVM groupVM; + + public void init(KiwiViewModelHandler viewModelHandler) { + this.viewModelHandler = viewModelHandler; + } + + // Event Listener on Button[#buttonCreateNewCompositoin].onAction + @FXML + public void createComposition(ActionEvent event) throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../createPanel/CreateComposition.fxml")); + Parent createCompositionPanel = loader.load(); + CreateCompositionController createCompositionController = loader.getController(); + Scene scene = new Scene(createCompositionPanel); + Stage compositionDialog = new Stage(); + createCompositionController.init(compositionDialog, this.viewModelHandler); + createCompositionController.setGroup(this.groupVM.getGroupName()); + compositionDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + compositionDialog.setScene(scene); + compositionDialog.setTitle("Komposition erstellen"); + compositionDialog.showAndWait(); + createCompositionController = null; + loader = null; + } + + public void addCompositionToPane(CompositionVM compositionVM) { + this.flowPaneCompositionList.getChildren() + .add( + new CompositionListItem(compositionVM, this.groupVM, this.viewModelHandler)); + } + + public void setCompositionRepository(GroupVM groupVM) { + this.groupVM = groupVM; + this.compositionRepositoryVM = groupVM.getCompositionRepositoryVM(); + this.flowPaneCompositionList.getChildren().clear(); + this.compositionRepositoryVM.compositionListProperty().forEach(compositionVM -> { + addCompositionToPane(compositionVM); + }); + + this.compositionRepositoryVM.compositionListProperty().addListener((ListChangeListener.Change<? extends CompositionVM> change) -> { + while(change.next()) { + change.getAddedSubList().forEach(compositionVM -> { + addCompositionToPane(compositionVM); + }); + + change.getRemoved().forEach(compositionVM -> { + int compositionListIndex = -1; + for(int i = 0; i < this.flowPaneCompositionList.getChildren().size(); i++) { + if(((CompositionListItem)this.flowPaneCompositionList + .getChildren() + .get(i)) + .getLightingMoodName() + .equals(compositionVM.getCompositionName())) { + compositionListIndex = i; + break; + } + } + if(compositionListIndex != -1) { + this.flowPaneCompositionList.getChildren().remove(compositionListIndex); + } + }); + } + }); + } +} diff --git a/src/lightControlSoftware/src/view/elements/ImageButton.java b/src/lightControlSoftware/src/view/elements/ImageButton.java new file mode 100644 index 0000000..b4eff18 --- /dev/null +++ b/src/lightControlSoftware/src/view/elements/ImageButton.java @@ -0,0 +1,27 @@ +package view.elements; + +import javafx.scene.control.Button; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; + +public class ImageButton extends Button{ + + Image icon; + ImageView imageView; + + public ImageButton(String imagePath) { + super(); + + this.icon = new Image(imagePath); + this.imageView = new ImageView(); + this.imageView.setImage(this.icon); + this.imageView.setFitHeight(25.0); + this.imageView.setFitWidth(25.0); + this.imageView.setPickOnBounds(true); + this.imageView.setPreserveRatio(true); + + this.setMnemonicParsing(false); + this.setStyle("-fx-background-color: none"); + this.setGraphic(imageView); + } +} diff --git a/src/lightControlSoftware/src/view/elements/NumberField.java b/src/lightControlSoftware/src/view/elements/NumberField.java new file mode 100644 index 0000000..ea13991 --- /dev/null +++ b/src/lightControlSoftware/src/view/elements/NumberField.java @@ -0,0 +1,57 @@ +package view.elements; + +import javafx.beans.binding.Bindings; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.TextField; +import javafx.util.converter.NumberStringConverter; + +public class NumberField extends TextField { + + private IntegerProperty number; + + public NumberField () { + initSpellListener(); + this.number = new SimpleIntegerProperty(1); + Bindings.bindBidirectional(this.textProperty(), this.number, new NumberStringConverter()); + + } + + public final void initSpellListener() { + this.textProperty().addListener((ObservableValue<? extends String> observable, String oldValue, String newValue) -> { + if (!newValue.matches("\\d*")) { + this.setText(newValue.replaceAll("[^\\d]", ""));/*The comma here "[^\\d,]" can be changed with the dot*/ + StringBuilder aus = new StringBuilder(); + aus.append(this.getText()); + boolean firstPointFound = false; + + for (int i = 0; i < aus.length(); i++) { + if (aus.charAt(i) == ',') {/*Change the , with . if you want the . to be the decimal separator*/ + if (!firstPointFound) { + firstPointFound = true; + } else { + aus.deleteCharAt(i); + } + } + } + newValue = aus.toString(); + this.setText(newValue); + } else { + this.setText(newValue); + } + }); + } + + public int getNumber() { + if(!this.getText().isEmpty()) { + return Integer.parseInt(this.getText()); + } else { + return 0; + } + } + + public IntegerProperty numberProperty() { + return this.number; + } +} diff --git a/src/lightControlSoftware/src/view/elements/ThreeStageButtonCallBack.java b/src/lightControlSoftware/src/view/elements/ThreeStageButtonCallBack.java new file mode 100644 index 0000000..e7a05c6 --- /dev/null +++ b/src/lightControlSoftware/src/view/elements/ThreeStageButtonCallBack.java @@ -0,0 +1,5 @@ +package view.elements; + +public interface ThreeStageButtonCallBack { + public void callClicked(); +} diff --git a/src/lightControlSoftware/src/view/elements/ThreeStageSwitchButton.java b/src/lightControlSoftware/src/view/elements/ThreeStageSwitchButton.java new file mode 100644 index 0000000..4aa4e4d --- /dev/null +++ b/src/lightControlSoftware/src/view/elements/ThreeStageSwitchButton.java @@ -0,0 +1,98 @@ +package view.elements; + +import javafx.animation.FillTransition; +import javafx.animation.ParallelTransition; +import javafx.animation.TranslateTransition; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; +import javafx.util.Duration; +import viewModel.group.GroupVM.State; + +public class ThreeStageSwitchButton extends Pane{ + + private ObjectProperty<State> buttonState = new SimpleObjectProperty<>(State.OFF); + private TranslateTransition translateAnimation = new TranslateTransition(Duration.seconds(0.25)); + private FillTransition fillAnimation = new FillTransition(Duration.seconds(0.25)); + private ParallelTransition animation = new ParallelTransition(translateAnimation, fillAnimation); + private ThreeStageButtonCallBack callbackObject; + + public ThreeStageSwitchButton() { + super.setPrefWidth(100); + super.setPrefHeight(50); + + Rectangle background = new Rectangle(100,50); + background.widthProperty().bind(super.prefWidthProperty()); + background.heightProperty().bind(super.prefHeightProperty()); + background.arcWidthProperty().bind(super.prefHeightProperty()); + background.arcHeightProperty().bind(super.prefHeightProperty()); + background.setFill(Color.WHITE); + background.setStroke(Color.LIGHTGRAY); + + Circle trigger = new Circle(25); + trigger.radiusProperty().bind(Bindings.divide(super.prefHeightProperty(), 2.1)); + trigger.centerXProperty().bind(Bindings.divide(super.prefHeightProperty(), 2)); + trigger.centerYProperty().bind(Bindings.divide(super.prefHeightProperty(), 2)); + trigger.setFill(Color.WHITE); + trigger.setStroke(Color.LIGHTGRAY); + + translateAnimation.setNode(trigger); + fillAnimation.setShape(background); + + getChildren().addAll(background, trigger); + + buttonState.addListener((obs, oldState, newState) -> { + Platform.runLater(() -> { + switch (newState) { + case ON: + translateAnimation.setToX(background.getWidth() - background.getArcHeight()); + fillAnimation.setFromValue(oldState == State.OFF ? Color.WHITE : Color.GOLD); + fillAnimation.setToValue(Color.LIGHTGREEN); + break; + case MID: + translateAnimation.setToX((background.getWidth() - background.getArcHeight())/2); + fillAnimation.setFromValue(oldState == State.OFF ? Color.WHITE : Color.LIGHTGREEN); + fillAnimation.setToValue(Color.GOLD); + break; + case OFF: + translateAnimation.setToX(0); + fillAnimation.setFromValue(oldState == State.ON ? Color.LIGHTGREEN : Color.GOLD); + fillAnimation.setToValue(Color.WHITE); + break; + } + animation.play(); + }); + }); + + setOnMouseClicked(event -> { + switch (buttonState.get()) { + case OFF: + buttonState.set(State.ON); + break; + case MID: + case ON: + buttonState.set(State.OFF); + break; + } + }); + } + public void setCallback(ThreeStageButtonCallBack callbackObject) { + this.callbackObject = callbackObject; + this.removeEventHandler(MouseEvent.MOUSE_CLICKED, getOnMouseClicked()); + setOnMouseClicked(event -> { + Platform.runLater(() -> { + this.callbackObject.callClicked(); + }); + }); + } + + public ObjectProperty<State> buttonStateProperty() { + return this.buttonState; + } +} diff --git a/src/lightControlSoftware/src/view/fixture/FixtureGroupListCell.java b/src/lightControlSoftware/src/view/fixture/FixtureGroupListCell.java new file mode 100644 index 0000000..6202167 --- /dev/null +++ b/src/lightControlSoftware/src/view/fixture/FixtureGroupListCell.java @@ -0,0 +1,22 @@ +package view.fixture; + +import javafx.scene.control.ListCell; + +public class FixtureGroupListCell extends ListCell<FixturesGroupListItem> { + + public FixtureGroupListCell() { + super(); + } + + @Override + protected void updateItem(FixturesGroupListItem item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setGraphic(null); + } else { + setGraphic(item); + } + } + +} diff --git a/src/lightControlSoftware/src/view/fixture/FixtureListCell.java b/src/lightControlSoftware/src/view/fixture/FixtureListCell.java new file mode 100644 index 0000000..35319d9 --- /dev/null +++ b/src/lightControlSoftware/src/view/fixture/FixtureListCell.java @@ -0,0 +1,22 @@ +package view.fixture; + +import javafx.scene.control.ListCell; + +public class FixtureListCell extends ListCell<FixturesListItem> { + + public FixtureListCell() { + super(); + } + + @Override + protected void updateItem(FixturesListItem item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setGraphic(null); + } else { + setGraphic(item); + } + } + +} diff --git a/src/lightControlSoftware/src/view/fixture/FixturesGroupListItem.java b/src/lightControlSoftware/src/view/fixture/FixturesGroupListItem.java new file mode 100644 index 0000000..91e8e01 --- /dev/null +++ b/src/lightControlSoftware/src/view/fixture/FixturesGroupListItem.java @@ -0,0 +1,138 @@ +package view.fixture; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import view.elements.ThreeStageSwitchButton; +import viewModel.fixture.FixtureGroupVM; +import viewModel.group.GroupVM.State; + + +public class FixturesGroupListItem extends AnchorPane{ + + private IntegerProperty groupId = new SimpleIntegerProperty(); + private StringProperty groupName = new SimpleStringProperty(); + private BooleanProperty fixtureInGroup = new SimpleBooleanProperty(false); + private BooleanProperty groupSelected = new SimpleBooleanProperty(false); + private FixtureGroupVM fixtureGroupVM; + + public FixturesGroupListItem(FixtureGroupVM fixtureGroupVM) { + this.fixtureGroupVM = fixtureGroupVM; + this.groupId.bind(this.fixtureGroupVM.groupIdProperty()); + this.groupName.bind(this.fixtureGroupVM.groupNameProperty()); + this.fixtureInGroup.bind(this.fixtureGroupVM.fixtureInGroupProperty()); + this.groupSelected.bind(this.fixtureGroupVM.groupSelectedProperty()); + + // style + setMaxHeight(50.0); + setMinHeight(50.0); + setPrefHeight(50.0); + VBox.setVgrow(this, Priority.ALWAYS); + VBox.setMargin(this, new Insets(5.0, 5.0, 5.0, 5.0)); + + VBox vBox = new VBox(); + vBox.setAlignment(Pos.CENTER_LEFT); + vBox.setLayoutX(7.0); + vBox.setLayoutY(2.0); + vBox.setMaxWidth(100.0); + AnchorPane.setBottomAnchor(vBox, 1.0); + AnchorPane.setLeftAnchor(vBox, 6.0); + AnchorPane.setTopAnchor(vBox, 1.0); + + Label lableGroupName = new Label(); + lableGroupName.textProperty().bind(groupName); + lableGroupName.setWrapText(true); + lableGroupName.getStyleClass().add("text-color-black"); + + HBox hBox = new HBox(); + hBox.setAlignment(Pos.CENTER_RIGHT); + AnchorPane.setBottomAnchor(hBox, 0.0); + AnchorPane.setRightAnchor(hBox, 10.0); + AnchorPane.setTopAnchor(hBox, 0.0); + + + ThreeStageSwitchButton switchButton = new ThreeStageSwitchButton(); + switchButton.setMaxHeight(20.0); + switchButton.setMinHeight(20.0); + switchButton.setPrefHeight(20.0); + switchButton.setMaxWidth(45.0); + switchButton.setMinWidth(45.0); + switchButton.setPrefWidth(45.0); + switchButton.buttonStateProperty().set(this.fixtureInGroup.get() ? State.ON : State.OFF); + this.fixtureInGroup.addListener((object, oldValue, newValue) -> { + if(newValue) { + switchButton.buttonStateProperty().setValue(State.ON); + } else { + switchButton.buttonStateProperty().setValue(State.OFF); + } + }); + + switchButton.buttonStateProperty().addListener((object, oldValue, newValue) -> { + if(newValue == State.ON && !this.fixtureInGroup.get()) { + this.fixtureGroupVM.fixtureInGroupProperty().setValue(true); + } else if(newValue == State.OFF && this.fixtureInGroup.get()) { + this.fixtureGroupVM.fixtureInGroupProperty().setValue(false); + } + });; + + + + vBox.getChildren().add(lableGroupName); + hBox.getChildren().add(switchButton); + getChildren().addAll(vBox,hBox); + getStyleClass().addAll("list-item", "item-diselected"); + + + this.setOnMouseClicked(event -> { + if(!this.groupSelected.get()) { + this.fixtureGroupVM.setGroupSelected(true); + } + + }); + + this.groupSelected.addListener((object, oldValue, newValue) -> { + if(newValue) { + this.getStyleClass().add("item-selected"); + this.getStyleClass().remove("item-diselected"); + } else { + this.getStyleClass().add("item-diselected"); + this.getStyleClass().remove("item-selected"); + } + }); + + } + + public byte getGroupId() { + return (byte) this.groupId.get(); + } + + public String getGroupName() { + return this.groupName.get(); + } + + public boolean isGroupSelected() { + return this.groupSelected.get(); + } + + public IntegerProperty groupIdProperty() { + return this.groupId; + } + + public StringProperty groupNameProperty() { + return this.groupName; + } + + public BooleanProperty groupSelectedProperty() { + return this.groupSelected; + } +} diff --git a/src/lightControlSoftware/src/view/fixture/FixturesListItem.java b/src/lightControlSoftware/src/view/fixture/FixturesListItem.java new file mode 100644 index 0000000..4198a15 --- /dev/null +++ b/src/lightControlSoftware/src/view/fixture/FixturesListItem.java @@ -0,0 +1,122 @@ +package view.fixture; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import view.elements.ThreeStageSwitchButton; +import viewModel.fixture.FixtureVM; +import viewModel.group.GroupVM.State; + + +public class FixturesListItem extends AnchorPane{ + + private StringProperty fixtureName = new SimpleStringProperty(); + private BooleanProperty fixtureInGroup = new SimpleBooleanProperty(false); + private BooleanProperty fixtureSelected = new SimpleBooleanProperty(false); + private FixtureVM fixtureVM; + + public FixturesListItem(FixtureVM fixtureVM) { + this.fixtureVM = fixtureVM; + this.fixtureName.bind(this.fixtureVM.fixtureNameProperty()); + this.fixtureInGroup.bind(this.fixtureVM.fixtureInGroupProperty()); + this.fixtureSelected.bind(this.fixtureVM.fixtureSelectedProperty()); + + // style + setMaxHeight(50.0); + setMinHeight(50.0); + setPrefHeight(50.0); + VBox.setVgrow(this, Priority.ALWAYS); + VBox.setMargin(this, new Insets(5.0, 5.0, 5.0, 5.0)); + + VBox vBox = new VBox(); + vBox.setAlignment(Pos.CENTER_LEFT); + vBox.setLayoutX(7.0); + vBox.setLayoutY(2.0); + vBox.setMaxWidth(100.0); + AnchorPane.setBottomAnchor(vBox, 1.0); + AnchorPane.setLeftAnchor(vBox, 6.0); + AnchorPane.setTopAnchor(vBox, 1.0); + + Label lableGroupName = new Label(); + lableGroupName.textProperty().bind(this.fixtureName); + lableGroupName.setWrapText(true); + lableGroupName.getStyleClass().add("text-color-black"); + + HBox hBox = new HBox(); + hBox.setAlignment(Pos.CENTER_RIGHT); + AnchorPane.setBottomAnchor(hBox, 0.0); + AnchorPane.setRightAnchor(hBox, 10.0); + AnchorPane.setTopAnchor(hBox, 0.0); + + + ThreeStageSwitchButton switchButton = new ThreeStageSwitchButton(); + switchButton.setMaxHeight(20.0); + switchButton.setMinHeight(20.0); + switchButton.setPrefHeight(20.0); + switchButton.setMaxWidth(45.0); + switchButton.setMinWidth(45.0); + switchButton.setPrefWidth(45.0); + switchButton.buttonStateProperty().set(this.fixtureInGroup.get() ? State.ON : State.OFF); + this.fixtureInGroup.addListener((object, oldValue, newValue) -> { + if(newValue) { + switchButton.buttonStateProperty().setValue(State.ON); + } else { + switchButton.buttonStateProperty().setValue(State.OFF); + } + }); + + switchButton.buttonStateProperty().addListener((object, oldValue, newValue) -> { + if(newValue == State.ON && !this.fixtureInGroup.get()) { + this.fixtureVM.fixtureInGroupProperty().setValue(true); + } else if(newValue == State.OFF && this.fixtureInGroup.get()) { + this.fixtureVM.fixtureInGroupProperty().setValue(false); + } + });; + + + + vBox.getChildren().add(lableGroupName); + hBox.getChildren().add(switchButton); + getChildren().addAll(vBox,hBox); + getStyleClass().addAll("list-item", "item-diselected"); + + + this.setOnMouseClicked(event -> { + if(!this.fixtureSelected.get()) { + this.fixtureVM.setFixtureSelected(true); + } + + }); + + this.fixtureSelected.addListener((object, oldValue, newValue) -> { + if(newValue) { + this.getStyleClass().add("item-selected"); + this.getStyleClass().remove("item-diselected"); + } else { + this.getStyleClass().add("item-diselected"); + this.getStyleClass().remove("item-selected"); + } + }); + } + + public String getFixtureName() { + return this.fixtureName.get(); + } + + public boolean isFixtureInGroup() { + return this.fixtureInGroup.get(); + } + + public FixtureVM getFixtureVM() { + return this.fixtureVM; + } + +} diff --git a/src/lightControlSoftware/src/view/fixture/createPanel/CreateFixture.fxml b/src/lightControlSoftware/src/view/fixture/createPanel/CreateFixture.fxml new file mode 100644 index 0000000..468e84a --- /dev/null +++ b/src/lightControlSoftware/src/view/fixture/createPanel/CreateFixture.fxml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import java.lang.String?> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> +<?import view.elements.NumberField?> + +<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="270.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.fixture.createPanel.CreateFixtureController"> + <center> + <BorderPane BorderPane.alignment="CENTER"> + <top> + <VBox BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER_LEFT"> + <VBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="10.0" /> + </VBox.margin> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Bezeichnung*"> + <HBox.margin> + <Insets left="5.0" right="15.0" /> + </HBox.margin> + </Label> + <HBox HBox.hgrow="ALWAYS"> + <children> + <AnchorPane HBox.hgrow="ALWAYS"> + <children> + <TextField fx:id="textboxName" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" /> + </children> + </AnchorPane> + </children> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </HBox> + </children> + </HBox> + <HBox alignment="CENTER_LEFT"> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Startadresse*"> + <HBox.margin> + <Insets left="5.0" right="15.0" /> + </HBox.margin> + </Label> + <HBox HBox.hgrow="ALWAYS"> + <children> + <AnchorPane HBox.hgrow="ALWAYS"> + <children> + <NumberField fx:id="numberboxAdress" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" /> + </children> + </AnchorPane> + </children> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </HBox> + </children> + <VBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </VBox.margin> + </HBox> + </children> + </VBox> + </top> + <center> + <AnchorPane BorderPane.alignment="CENTER"> + <children> + <AnchorPane styleClass="section" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="15.0"> + <children> + <VBox layoutX="7.0" layoutY="12.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <children> + <Button fx:id="buttonCreateGroup" mnemonicParsing="false" onAction="#createGroup" text="Neue Gruppe"> + <VBox.margin> + <Insets bottom="5.0" left="5.0" top="5.0" /> + </VBox.margin> + </Button> + <ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS"> + <content> + <ListView fx:id="groupList"> + <styleClass> + <String fx:value="white-background" /> + <String fx:value="list-cell" /> + </styleClass> + </ListView> + </content> + </ScrollPane> + </children> + </VBox> + </children> + </AnchorPane> + <Label styleClass="section-headline" text="Gruppenzuordnung" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="7.0"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Label> + </children> + </AnchorPane> + </center> + <bottom> + <HBox alignment="CENTER" BorderPane.alignment="CENTER"> + <children> + <Button fx:id="buttonCreateLamp" mnemonicParsing="false" onAction="#createLamp" text="Lampe hinzufügen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + <Button fx:id="buttonCancel" mnemonicParsing="false" onAction="#cancel" text="Abbrechen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + </children> + </HBox> + </bottom> + </BorderPane> + </center> +</BorderPane> diff --git a/src/lightControlSoftware/src/view/fixture/createPanel/CreateFixtureController.java b/src/lightControlSoftware/src/view/fixture/createPanel/CreateFixtureController.java new file mode 100644 index 0000000..dfa2887 --- /dev/null +++ b/src/lightControlSoftware/src/view/fixture/createPanel/CreateFixtureController.java @@ -0,0 +1,175 @@ +package view.fixture.createPanel; + +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ListView; +import javafx.scene.control.TextField; +import javafx.scene.control.TextInputDialog; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.image.Image; + +import java.util.Optional; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; +import view.elements.NumberField; +import view.fixture.FixtureGroupListCell; +import view.fixture.FixturesGroupListItem; +import viewModel.KiwiViewModelHandler; +import viewModel.fixture.FixtureGroupRepositoryVM; +import viewModel.fixture.FixtureGroupVM; +import viewModel.fixture.FixtureRepositoryVM; +import viewModel.fixture.FixtureVM; + +public class CreateFixtureController { + @FXML + private BorderPane root; + @FXML + private TextField textboxName; + @FXML + private NumberField numberboxAdress; + @FXML + private Button buttonCreateGroup; + @FXML + private ListView<FixturesGroupListItem> groupList; + @FXML + private Button buttonCreateLamp; + @FXML + private Button buttonCancel; + + private Stage stage; + private FixtureRepositoryVM fixtureRepositoryVM; + private FixtureGroupRepositoryVM fixtureGroupRepositoryVM; + private ObservableList<FixturesGroupListItem> groups = FXCollections.observableArrayList(); + + public void init(Stage stage, KiwiViewModelHandler viewModelHandler) { + this.stage = stage; + this.stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + this.fixtureRepositoryVM = viewModelHandler.getFixtureRepositoryVM(); + this.fixtureGroupRepositoryVM = viewModelHandler.getFixtureGroupRepositoryVM(); + this.loadGroups(); + + this.fixtureGroupRepositoryVM.fixtureGroupsListProperty().addListener((ListChangeListener.Change<? extends FixtureGroupVM> change) -> { + while(change.next()) { + change.getAddedSubList().forEach(fixtureGroupVM -> { + this.groups.add(new FixturesGroupListItem(fixtureGroupVM)); + }); + + change.getRemoved().forEach(groupVM -> { + int groupListIndex = -1; + for(int i = 0; i < this.groups.size(); i++) { + if(this.groups.get(i).getGroupName().equals(groupVM.getGroupName())) { + groupListIndex = i; + break; + } + } + if(groupListIndex != -1) { + this.groups.remove(groupListIndex); + } + }); + } + }); + + this.groupList.setItems(this.groups); + this.groupList.setCellFactory(param -> new FixtureGroupListCell()); + + } + + // Event Listener on Button[#buttonCreateGroup].onAction + @FXML + public void createGroup(ActionEvent event) { + TextInputDialog dialog = new TextInputDialog("Neue Gruppe"); + dialog.setTitle("Neue Gruppe"); + dialog.setHeaderText("Neue Gruppe erstellen"); + dialog.setContentText("Bitte geben Sie den Namen der neuen Gruppe ein:"); + Optional<String> result = dialog.showAndWait(); + result.ifPresent(name -> { + try { + this.fixtureGroupRepositoryVM.newGroup(name); + } catch (IllegalArgumentException e) { + Alert alert = new Alert(AlertType.ERROR); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Fehler"); + alert.setHeaderText("Fehler beim Erstellen einer Lampe."); + alert.setContentText(e.getMessage()); + alert.showAndWait(); + } + + }); + } + // Event Listener on Button[#buttonCreateLamp].onAction + @FXML + public void createLamp(ActionEvent event) { + if(this.isDataValid()) { + try { + this.fixtureRepositoryVM.newFixture(this.textboxName.getText(), this.numberboxAdress.getNumber()); + FixtureVM fixtureVM = this.fixtureRepositoryVM.getFixtureVM(this.textboxName.getText()); + this.fixtureGroupRepositoryVM.fixtureGroupsListProperty().forEach( fixtureGroup -> { + if(fixtureGroup.isFixtureInGroup()) { + fixtureGroup.addFixtureToGroup(fixtureVM); + } + }); + + + this.stage.close(); + } catch (IllegalArgumentException e) { + Alert alert = new Alert(AlertType.ERROR); + alert.setTitle("Fehler"); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setHeaderText("Fehler beim Erstellen einer Lampe."); + alert.setContentText(e.getMessage()); + alert.showAndWait(); + } + } + + } + // Event Listener on Button[#buttonCancel].onAction + @FXML + public void cancel(ActionEvent event) { + this.stage.close(); + } + + private boolean isDataValid() { + boolean isValid = false; + String headerText = ""; + String contentText = ""; + + if(this.textboxName.getText().isEmpty()) { + headerText = "Fehlerhafter Name."; + contentText = "Das Namensfeld darf nicht leer sein."; + } else if(this.numberboxAdress.getText().isEmpty()) { + headerText = "Fehlerhafte Adresse."; + contentText = "Das Adressefeld darf nicht leer sein."; + } else if(this.numberboxAdress.getNumber() > 512 || this.numberboxAdress.getNumber() < 1) { + headerText = "Fehlerhafte Adresse."; + contentText = "Das Adresse der Lampe muss ziwschen 1 und 512 liegen."; + } else { + isValid = true; + } + + if(!isValid) { + Alert alert = new Alert(AlertType.WARNING); + alert.setTitle("Warnung"); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.showAndWait(); + } + + return isValid; + } + + public void loadGroups() { + this.fixtureGroupRepositoryVM.fixtureGroupsListProperty().forEach(fixtureGroupVM -> { + this.groups.add(new FixturesGroupListItem(fixtureGroupVM)); + }); + } +} diff --git a/src/lightControlSoftware/src/view/fixture/editPanel/EditDeleteFixture.fxml b/src/lightControlSoftware/src/view/fixture/editPanel/EditDeleteFixture.fxml new file mode 100644 index 0000000..7104692 --- /dev/null +++ b/src/lightControlSoftware/src/view/fixture/editPanel/EditDeleteFixture.fxml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import java.lang.String?> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ComboBox?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> +<?import view.elements.NumberField?> + +<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="300.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.fixture.editPanel.EditDeleteFixtureController"> + <center> + <BorderPane BorderPane.alignment="CENTER"> + <top> + <VBox BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER_LEFT"> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Lampe"> + <HBox.margin> + <Insets left="5.0" right="15.0" /> + </HBox.margin> + </Label> + <HBox HBox.hgrow="ALWAYS"> + <children> + <AnchorPane HBox.hgrow="ALWAYS"> + <children> + <VBox alignment="CENTER_LEFT" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="45.0" AnchorPane.topAnchor="0.0"> + <children> + <AnchorPane> + <children> + <ComboBox fx:id="comboboxFixture" onAction="#selectFixture" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" /> + </children> + </AnchorPane> + </children> + </VBox> + <Button fx:id="buttonDelete" mnemonicParsing="false" onAction="#deleteFixture" style="-fx-background-color: none;" AnchorPane.rightAnchor="0.0"> + <graphic> + <ImageView fitHeight="20.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@../../../../ressourcen/images/Delete.png" /> + </image> + </ImageView> + </graphic> + </Button> + </children> + </AnchorPane> + </children> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </HBox> + </children> + <VBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="10.0" /> + </VBox.margin> + </HBox> + <HBox alignment="CENTER_LEFT"> + <VBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </VBox.margin> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Bezeichnung*"> + <HBox.margin> + <Insets left="5.0" right="15.0" /> + </HBox.margin> + </Label> + <HBox HBox.hgrow="ALWAYS"> + <children> + <AnchorPane HBox.hgrow="ALWAYS"> + <children> + <TextField fx:id="textboxName" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" /> + </children> + </AnchorPane> + </children> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </HBox> + </children> + </HBox> + <HBox alignment="CENTER_LEFT"> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Startadresse*"> + <HBox.margin> + <Insets left="5.0" right="15.0" /> + </HBox.margin> + </Label> + <HBox HBox.hgrow="ALWAYS"> + <children> + <AnchorPane HBox.hgrow="ALWAYS"> + <children> + <NumberField fx:id="numberboxAdress" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" /> + </children> + </AnchorPane> + </children> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </HBox> + </children> + <VBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </VBox.margin> + </HBox> + </children> + </VBox> + </top> + <center> + <AnchorPane BorderPane.alignment="CENTER"> + <children> + <AnchorPane styleClass="section" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="15.0"> + <children> + <VBox layoutX="7.0" layoutY="12.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <children> + <Button fx:id="buttonCreateGroup" mnemonicParsing="false" onAction="#createGroup" text="Neue Gruppe"> + <VBox.margin> + <Insets bottom="5.0" left="5.0" top="5.0" /> + </VBox.margin> + </Button> + <ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS"> + <content> + <ListView fx:id="groupList"> + <styleClass> + <String fx:value="white-background" /> + <String fx:value="list-cell" /> + </styleClass> + </ListView> + </content> + </ScrollPane> + </children> + </VBox> + </children> + </AnchorPane> + <Label styleClass="section-headline" text="Gruppenzuordnung" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="7.0"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Label> + </children> + </AnchorPane> + </center> + <bottom> + <HBox alignment="CENTER" BorderPane.alignment="CENTER"> + <children> + <Button fx:id="buttonSave" mnemonicParsing="false" onAction="#save" text="Speichern"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + <Button fx:id="buttonCancel" mnemonicParsing="false" onAction="#cancel" text="Abbrechen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + </children> + </HBox> + </bottom> + </BorderPane> + </center> +</BorderPane> diff --git a/src/lightControlSoftware/src/view/fixture/editPanel/EditDeleteFixtureController.java b/src/lightControlSoftware/src/view/fixture/editPanel/EditDeleteFixtureController.java new file mode 100644 index 0000000..b0c1872 --- /dev/null +++ b/src/lightControlSoftware/src/view/fixture/editPanel/EditDeleteFixtureController.java @@ -0,0 +1,236 @@ +package view.fixture.editPanel; + +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.TextField; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.image.Image; + +import java.util.Optional; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; + +import javafx.scene.control.ListView; + +import javafx.scene.control.ComboBox; + +import view.elements.NumberField; +import view.fixture.FixtureGroupListCell; +import view.fixture.FixturesGroupListItem; +import viewModel.KiwiViewModelHandler; +import viewModel.fixture.FixtureGroupRepositoryVM; +import viewModel.fixture.FixtureGroupVM; +import viewModel.fixture.FixtureRepositoryVM; +import viewModel.fixture.FixtureVM; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; + +public class EditDeleteFixtureController { + @FXML + private BorderPane root; + @FXML + private ComboBox<String> comboboxFixture; + @FXML + private Button buttonDelete; + @FXML + private TextField textboxName; + @FXML + private NumberField numberboxAdress; + @FXML + private Button buttonCreateGroup; + @FXML + private ListView<FixturesGroupListItem> groupList; + @FXML + private Button buttonSave; + @FXML + private Button buttonCancel; + + private Stage stage; + private FixtureRepositoryVM fixtureRepositoryVM; + private FixtureGroupRepositoryVM fixtureGroupRepositoryVM; + private FixtureVM fixtureVM; + private ObservableList<String> fixtureNames = FXCollections.observableArrayList(); + private ObservableList<FixturesGroupListItem> groups = FXCollections.observableArrayList(); + + public void init(Stage stage, KiwiViewModelHandler viewModelHandler) { + this.stage = stage; + this.fixtureRepositoryVM = viewModelHandler.getFixtureRepositoryVM(); + this.fixtureGroupRepositoryVM = viewModelHandler.getFixtureGroupRepositoryVM(); + + this.loadFixtures(); + this.loadGroups(); + + this.fixtureGroupRepositoryVM.fixtureGroupsListProperty().addListener((ListChangeListener.Change<? extends FixtureGroupVM> change) -> { + while(change.next()) { + change.getAddedSubList().forEach(fixtureGroupVM -> { + this.groups.add(new FixturesGroupListItem(fixtureGroupVM)); + }); + + change.getRemoved().forEach(groupVM -> { + int groupListIndex = -1; + for(int i = 0; i < this.groups.size(); i++) { + if(this.groups.get(i).getGroupName().equals(groupVM.getGroupName())) { + groupListIndex = i; + break; + } + } + if(groupListIndex != -1) { + this.groups.remove(groupListIndex); + } + }); + } + }); + this.groupList.setItems(this.groups); + this.groupList.setCellFactory(param -> new FixtureGroupListCell()); + this.updateFixturesGroups(); + } + + // Event Listener on ComboBox[#comboboxFixture].onAction + @FXML + public void selectFixture(ActionEvent event) { + if(!(this.comboboxFixture.getValue() == null)) { + this.fixtureVM = fixtureRepositoryVM.getFixtureVM(this.comboboxFixture.getValue()); + this.textboxName.setText(this.fixtureVM.getFixtureName()); + this.numberboxAdress.setText(String.valueOf(this.fixtureVM.getFixtureAddress())); + this.updateFixturesGroups(); + } + + } + // Event Listener on Button[#buttonDelete].onAction + @FXML + public void deleteFixture(ActionEvent event) { + Alert alert = new Alert(AlertType.CONFIRMATION); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Lampe l�schen"); + alert.setHeaderText(String.format("Sind Sie sicher, dass Sie die Lampe %s l�schen m�chten", this.fixtureVM.getFixtureName())); + ButtonType buttonDelete = new ButtonType("Lampe l�schen"); + ButtonType buttonCancel = new ButtonType("Abbrechen", ButtonData.CANCEL_CLOSE); + alert.getButtonTypes().setAll(buttonDelete, buttonCancel); + Optional<ButtonType> result = alert.showAndWait(); + if (result.orElseThrow() == buttonDelete) { + this.fixtureRepositoryVM.deleteFixture(this.fixtureVM.getFixtureName()); + this.loadFixtures(); + } + } + // Event Listener on Button[#buttonCreateGroup].onAction + @FXML + public void createGroup(ActionEvent event) { + // TODO Autogenerated + } + // Event Listener on Button[#buttonCreateLamp].onAction + @FXML + public void save(ActionEvent event) { + if(this.isDataValid()) { + this.fixtureVM.setFixtureName(this.textboxName.getText()); + this.fixtureVM.setFixtureAddress(this.numberboxAdress.getNumber()); + + this.fixtureGroupRepositoryVM.fixtureGroupsListProperty().forEach(fixtureGroup -> { + boolean fixtureInGroup = fixtureGroup.getFixtureRepositoryVM() + .fixtureListProperty() + .stream() + .filter(fixtureVM -> fixtureVM.getFixtureName().equals(this.fixtureVM.getFixtureName())) + .findFirst() + .isPresent(); + + if(fixtureInGroup && !fixtureGroup.isFixtureInGroup()) { + fixtureGroup.removeFixtureFromGroup(this.fixtureVM); + } else if (!fixtureInGroup && fixtureGroup.isFixtureInGroup()){ + fixtureGroup.addFixtureToGroup(this.fixtureVM); + } + }); + this.stage.close(); + } + } + // Event Listener on Button[#buttonCancel].onAction + @FXML + public void cancel(ActionEvent event) { + this.stage.close(); + } + + private boolean isDataValid () { + boolean isValid = false; + String headerText = ""; + String contentText = ""; + + if(this.textboxName.getText().isEmpty()) { + headerText = "Fehlerhafter Name."; + contentText = "Die Namensfeld darf nicht leer sein."; + } else if(nameAllreadyExist(this.textboxName.getText())) { + headerText = "Fehlerhafter Name."; + contentText = "Der Name ist bereits in Benutzung."; + } else if(this.numberboxAdress.getText().isEmpty()) { + headerText = "Fehlerhafte Adresse."; + contentText = "Die Adressefeld darf nicht leer sein."; + } else if(this.numberboxAdress.getNumber() > 512 || this.numberboxAdress.getNumber() < 1) { + headerText = "Fehlerhafte Adresse."; + contentText = "Die Adresse der Lampe muss ziwschen 1 und 512 liegen."; + } else { + isValid = true; + } + + if(!isValid) { + Alert alert = new Alert(AlertType.WARNING); + alert.setTitle("Warnung"); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.showAndWait(); + } + + return isValid; + } + + private boolean nameAllreadyExist(String name) { + boolean nameExist = false; + if(!this.fixtureVM.getFixtureName().equals(name) && + this.fixtureNames.stream() + .filter(fixtureName -> fixtureName.equals(name)) + .count() > 0){ + nameExist = true; + } + + return nameExist; + } + + private void loadGroups() { + this.fixtureGroupRepositoryVM.fixtureGroupsListProperty().forEach(fixtureGroupVM -> { + this.groups.add(new FixturesGroupListItem(fixtureGroupVM)); + }); + } + + private void loadFixtures() { + this.fixtureNames.clear(); + this.fixtureRepositoryVM.fixturesRepositoryProperty().forEach(fixtureVM -> { + this.fixtureNames.add(fixtureVM.getFixtureName()); + }); + this.fixtureVM = fixtureRepositoryVM.getFixtureVM(this.fixtureNames.get(0)); + this.comboboxFixture.setItems(this.fixtureNames); + this.comboboxFixture.setValue(this.fixtureNames.get(0)); + this.textboxName.setText(this.fixtureVM.getFixtureName()); + this.numberboxAdress.setText(String.valueOf(this.fixtureVM.getFixtureAddress())); + } + + private void updateFixturesGroups() { + this.fixtureGroupRepositoryVM.fixtureGroupsListProperty().forEach(fixtureGroup -> { + boolean fixtureInGroup = fixtureGroup.getFixtureRepositoryVM() + .fixtureListProperty() + .stream() + .filter(fixtureVM -> fixtureVM.getFixtureName().equals(this.fixtureVM.getFixtureName())) + .findFirst() + .isPresent(); + + if(fixtureInGroup) { + fixtureGroup.setFixtureInGroup(true); + } else { + fixtureGroup.setFixtureInGroup(false); + } + }); + } + +} diff --git a/src/lightControlSoftware/src/view/group/GroupListCell.java b/src/lightControlSoftware/src/view/group/GroupListCell.java new file mode 100644 index 0000000..d252764 --- /dev/null +++ b/src/lightControlSoftware/src/view/group/GroupListCell.java @@ -0,0 +1,22 @@ +package view.group; + +import javafx.scene.control.ListCell; + +public class GroupListCell extends ListCell<GroupListItem> { + + public GroupListCell() { + super(); + } + + @Override + protected void updateItem(GroupListItem item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setGraphic(null); + } else { + setGraphic(item); + } + } + +} diff --git a/src/lightControlSoftware/src/view/group/GroupListItem.java b/src/lightControlSoftware/src/view/group/GroupListItem.java new file mode 100644 index 0000000..e6bbb22 --- /dev/null +++ b/src/lightControlSoftware/src/view/group/GroupListItem.java @@ -0,0 +1,183 @@ +package view.group; + +import java.io.IOException; +import java.util.Optional; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.image.Image; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import view.elements.ImageButton; +import view.elements.ThreeStageSwitchButton; +import view.group.editPanel.EditDeleteGroupController; +import viewModel.KiwiViewModelHandler; +import viewModel.group.GroupRepositoryVM; +import viewModel.group.GroupVM; +import viewModel.group.GroupVM.State; + + +public class GroupListItem extends AnchorPane{ + + private IntegerProperty groupId = new SimpleIntegerProperty(); + private StringProperty groupName = new SimpleStringProperty(); + private ObjectProperty<State> groupState = new SimpleObjectProperty<>(State.OFF); + private BooleanProperty groupSelected = new SimpleBooleanProperty(false); + private GroupVM groupVM; + private GroupRepositoryVM groupRepositoryVM; + + public GroupListItem(GroupVM groupVM, KiwiViewModelHandler viewModelHandler) { + this.groupVM = groupVM; + this.groupRepositoryVM = viewModelHandler.getGroupRepositoryVM(); + this.groupId.bind(this.groupVM.groupIdProperty()); + this.groupName.bind(this.groupVM.groupNameProperty()); + this.groupState.bindBidirectional(this.groupVM.groupStateProperty());; + this.groupSelected.bind(this.groupVM.groupSelectedProperty()); + + // style + setMaxHeight(50.0); + setMinHeight(50.0); + setPrefHeight(50.0); + VBox.setVgrow(this, Priority.ALWAYS); + VBox.setMargin(this, new Insets(5.0, 5.0, 5.0, 5.0)); + + VBox vBox = new VBox(); + vBox.setAlignment(Pos.CENTER_LEFT); + vBox.setLayoutX(7.0); + vBox.setLayoutY(2.0); + vBox.setMaxWidth(100.0); + AnchorPane.setBottomAnchor(vBox, 1.0); + AnchorPane.setLeftAnchor(vBox, 6.0); + AnchorPane.setTopAnchor(vBox, 1.0); + + Label lableGroupName = new Label(); + lableGroupName.textProperty().bind(groupName); + lableGroupName.setWrapText(true); + lableGroupName.getStyleClass().add("text-color-black"); + + HBox hBox = new HBox(); + hBox.setAlignment(Pos.CENTER_RIGHT); + AnchorPane.setBottomAnchor(hBox, 0.0); + AnchorPane.setRightAnchor(hBox, 0.0); + AnchorPane.setTopAnchor(hBox, 0.0); + + ThreeStageSwitchButton switchButton = new ThreeStageSwitchButton(); + switchButton.setMaxHeight(20.0); + switchButton.setMinHeight(20.0); + switchButton.setPrefHeight(20.0); + switchButton.setMaxWidth(45.0); + switchButton.setMinWidth(45.0); + switchButton.setPrefWidth(45.0); + switchButton.buttonStateProperty().bindBidirectional(this.groupState); + + ImageButton buttonEditGroup = new ImageButton("images/Edit.png"); + buttonEditGroup.setOnAction(event -> { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../group/editPanel/EditDeleteGroup.fxml")); + Parent editDeleteGroupPanel; + editDeleteGroupPanel = loader.load(); + EditDeleteGroupController editDeleteGroupController = loader.getController(); + Scene scene = new Scene(editDeleteGroupPanel); + Stage groupDialog = new Stage(); + editDeleteGroupController.init(groupDialog, viewModelHandler); + editDeleteGroupController.setGroup(groupVM); + groupDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + groupDialog.setScene(scene); + groupDialog.setTitle("Gruppe �ndern/l�schen"); + groupDialog.showAndWait(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + }); + + ImageButton buttonDeleteGroup = new ImageButton("images/Delete.png"); + buttonDeleteGroup.setOnAction(event -> { + Alert alert = new Alert(AlertType.CONFIRMATION); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Gruppe l�schen"); + alert.setHeaderText(String.format("Sind Sie sicher, dass Sie die Gruppe %s l�schen m�chten", this.getGroupName())); + ButtonType buttonDelete = new ButtonType("Gruppe l�schen"); + ButtonType buttonCancel = new ButtonType("Abbrechen", ButtonData.CANCEL_CLOSE); + alert.getButtonTypes().setAll(buttonDelete, buttonCancel); + Optional<ButtonType> result = alert.showAndWait(); + if (result.orElseThrow() == buttonDelete) { + groupRepositoryVM.deleteGroup(this.getGroupName()); + } + }); + + + vBox.getChildren().add(lableGroupName); + hBox.getChildren().addAll(switchButton, buttonEditGroup, buttonDeleteGroup); + getChildren().addAll(vBox,hBox); + getStyleClass().addAll("list-item", "item-diselected"); + + + this.setOnMouseClicked(event -> { + if(!this.groupSelected.get()) { + this.groupVM.setGroupSelected(true); + } + + }); + + this.groupSelected.addListener((object, oldValue, newValue) -> { + if(newValue) { + this.getStyleClass().add("item-selected"); + this.getStyleClass().remove("item-diselected"); + } else { + this.getStyleClass().add("item-diselected"); + this.getStyleClass().remove("item-selected"); + } + }); + + } + + public byte getGroupId() { + return (byte) this.groupId.get(); + } + + public String getGroupName() { + return this.groupName.get(); + } + + public boolean isGroupSelected() { + return this.groupSelected.get(); + } + + public void selectedGroup() { + this.groupVM.groupSelectedProperty().set(true); + } + + public IntegerProperty groupIdProperty() { + return this.groupId; + } + + public StringProperty groupNameProperty() { + return this.groupName; + } + + public BooleanProperty groupSelectedProperty() { + return this.groupSelected; + } +} diff --git a/src/lightControlSoftware/src/view/group/createPanel/CreateGroup.fxml b/src/lightControlSoftware/src/view/group/createPanel/CreateGroup.fxml new file mode 100644 index 0000000..5f34aaa --- /dev/null +++ b/src/lightControlSoftware/src/view/group/createPanel/CreateGroup.fxml @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import java.lang.String?> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> + +<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="270.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.group.createPanel.CreateGroupController"> + <center> + <BorderPane BorderPane.alignment="CENTER"> + <top> + <VBox BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER_LEFT"> + <VBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="10.0" /> + </VBox.margin> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Bezeichnung*"> + <HBox.margin> + <Insets left="5.0" right="15.0" /> + </HBox.margin> + </Label> + <HBox HBox.hgrow="ALWAYS"> + <children> + <AnchorPane HBox.hgrow="ALWAYS"> + <children> + <TextField fx:id="textboxName" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" /> + </children> + </AnchorPane> + </children> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </HBox> + </children> + </HBox> + </children> + </VBox> + </top> + <center> + <AnchorPane BorderPane.alignment="CENTER"> + <children> + <AnchorPane styleClass="section" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="15.0"> + <children> + <VBox layoutX="7.0" layoutY="12.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <children> + <ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS"> + <content> + <ListView fx:id="groupList"> + <styleClass> + <String fx:value="white-background" /> + <String fx:value="list-cell" /> + </styleClass> + </ListView> + </content> + </ScrollPane> + </children> + </VBox> + </children> + </AnchorPane> + <Label styleClass="section-headline" text="Lampenzuordnung" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="7.0"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Label> + </children> + </AnchorPane> + </center> + <bottom> + <HBox alignment="CENTER" BorderPane.alignment="CENTER"> + <children> + <Button fx:id="buttonCreateGroup" mnemonicParsing="false" onAction="#createGroup" text="Gruppe hinzufügen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + <Button fx:id="buttonCancel" mnemonicParsing="false" onAction="#cancel" text="Abbrechen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + </children> + </HBox> + </bottom> + </BorderPane> + </center> +</BorderPane> diff --git a/src/lightControlSoftware/src/view/group/createPanel/CreateGroupController.java b/src/lightControlSoftware/src/view/group/createPanel/CreateGroupController.java new file mode 100644 index 0000000..98224e1 --- /dev/null +++ b/src/lightControlSoftware/src/view/group/createPanel/CreateGroupController.java @@ -0,0 +1,137 @@ +package view.group.createPanel; + +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; + +import javafx.scene.control.TextField; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.image.Image; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; + +import javafx.scene.control.ListView; + +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; +import view.fixture.FixtureListCell; +import view.fixture.FixturesListItem; +import viewModel.KiwiViewModelHandler; +import viewModel.fixture.FixtureRepositoryVM; +import viewModel.fixture.FixtureVM; +import viewModel.group.GroupRepositoryVM; +import viewModel.group.GroupVM; + +public class CreateGroupController { + @FXML + private BorderPane root; + @FXML + private TextField textboxName; + @FXML + private ListView<FixturesListItem> groupList; + @FXML + private Button buttonCreateGroup; + @FXML + private Button buttonCancel; + + private FixtureRepositoryVM fixtureRepositoryVM; + private GroupRepositoryVM groupRepositoryVM; + private ObservableList<FixturesListItem> fixtures = FXCollections.observableArrayList(); + private Stage stage; + + public void init(Stage stage, KiwiViewModelHandler viewModelHandler) { + this.stage = stage; + this.fixtureRepositoryVM = viewModelHandler.getFixtureRepositoryVM(); + this.groupRepositoryVM = viewModelHandler.getGroupRepositoryVM(); + this.loadFixtures(); + + this.fixtureRepositoryVM.fixtureListProperty().addListener((ListChangeListener.Change<? extends FixtureVM> change) -> { + while(change.next()) { + change.getAddedSubList().forEach(fixtureVM -> { + this.fixtures.add(new FixturesListItem(fixtureVM)); + }); + + change.getRemoved().forEach(fixtureVM -> { + int fixtureListIndex = -1; + for(int i = 0; i < this.fixtures.size(); i++) { + if(this.fixtures.get(i).getFixtureName().equals(fixtureVM.getFixtureName())) { + fixtureListIndex = i; + break; + } + } + if(fixtureListIndex != -1) { + this.fixtures.remove(fixtureListIndex); + } + }); + } + }); + + + this.groupList.setItems(this.fixtures); + this.groupList.setCellFactory(param -> new FixtureListCell()); + } + + private boolean isDataValid() { + boolean isValid = false; + String headerText = ""; + String contentText = ""; + + if(this.textboxName.getText().isEmpty()) { + headerText = "Fehlerhafter Name."; + contentText = "Das Namensfeld darf nicht leer sein."; + } else { + isValid = true; + } + + if(!isValid) { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.showAndWait(); + } + + return isValid; + } + + // Event Listener on Button[#buttonCreateGroup].onAction + @FXML + public void createGroup(ActionEvent event) { + if(isDataValid()) { + try { + this.groupRepositoryVM.newGroup(this.textboxName.getText()); + GroupVM groupVM = this.groupRepositoryVM.getGroupVM(this.textboxName.getText()); + this.fixtures.forEach(fixtureListItem -> { + if(fixtureListItem.isFixtureInGroup()) { + groupVM.addFixture(fixtureListItem.getFixtureVM()); + } + }); + this.stage.close(); + } catch (IllegalArgumentException e) { + Alert alert = new Alert(AlertType.ERROR); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Fehler"); + alert.setHeaderText("Fehler beim Erstellen einer Gruppe."); + alert.setContentText(e.getMessage()); + alert.showAndWait(); + } + } + } + // Event Listener on Button[#buttonCancel].onAction + @FXML + public void cancel(ActionEvent event) { + this.stage.close(); + } + + public void loadFixtures() { + this.fixtureRepositoryVM.fixturesRepositoryProperty().forEach(fixtureVM -> { + this.fixtures.add(new FixturesListItem(fixtureVM)); + }); + } + +} diff --git a/src/lightControlSoftware/src/view/group/editPanel/EditDeleteGroup.fxml b/src/lightControlSoftware/src/view/group/editPanel/EditDeleteGroup.fxml new file mode 100644 index 0000000..5be9662 --- /dev/null +++ b/src/lightControlSoftware/src/view/group/editPanel/EditDeleteGroup.fxml @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import java.lang.String?> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ComboBox?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> + +<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="320.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.group.editPanel.EditDeleteGroupController"> + <center> + <BorderPane BorderPane.alignment="CENTER"> + <top> + <VBox BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER_LEFT"> + <VBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="10.0" /> + </VBox.margin> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Gruppe"> + <HBox.margin> + <Insets left="5.0" right="15.0" /> + </HBox.margin> + </Label> + <HBox HBox.hgrow="ALWAYS"> + <children> + <AnchorPane HBox.hgrow="ALWAYS"> + <children> + <VBox alignment="BOTTOM_LEFT" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="45.0"> + <children> + <AnchorPane> + <children> + <ComboBox fx:id="comboboxGroup" onAction="#selectGroup" prefWidth="150.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" /> + </children> + </AnchorPane> + </children> + </VBox> + <Button fx:id="buttonDelete" mnemonicParsing="false" onAction="#deleteGroup" style="-fx-background-color: none;" AnchorPane.rightAnchor="0.0"> + <graphic> + <ImageView fitHeight="20.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@../../../../ressourcen/images/Delete.png" /> + </image> + </ImageView> + </graphic> + </Button> + </children> + </AnchorPane> + </children> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </HBox> + </children> + </HBox> + <HBox alignment="CENTER_LEFT"> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Bezeichnung*"> + <HBox.margin> + <Insets left="5.0" right="15.0" /> + </HBox.margin> + </Label> + <HBox HBox.hgrow="ALWAYS"> + <children> + <AnchorPane HBox.hgrow="ALWAYS"> + <children> + <TextField fx:id="textboxName" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" /> + </children> + </AnchorPane> + </children> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </HBox> + </children> + <VBox.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </VBox.margin> + </HBox> + </children> + </VBox> + </top> + <center> + <AnchorPane BorderPane.alignment="CENTER"> + <children> + <AnchorPane styleClass="section" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="15.0"> + <children> + <VBox layoutX="7.0" layoutY="12.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <children> + <ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS"> + <content> + <ListView fx:id="groupList"> + <styleClass> + <String fx:value="white-background" /> + <String fx:value="list-cell" /> + </styleClass> + </ListView> + </content> + </ScrollPane> + </children> + </VBox> + </children> + </AnchorPane> + <Label styleClass="section-headline" text="Lampenzuordnung" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="7.0"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Label> + </children> + </AnchorPane> + </center> + <bottom> + <HBox alignment="CENTER" BorderPane.alignment="CENTER"> + <children> + <Button fx:id="buttonCreateGroup" mnemonicParsing="false" onAction="#createGroup" text="Speichern"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + <Button fx:id="buttonCancel" mnemonicParsing="false" onAction="#cancel" text="Abbrechen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + </children> + </HBox> + </bottom> + </BorderPane> + </center> +</BorderPane> diff --git a/src/lightControlSoftware/src/view/group/editPanel/EditDeleteGroupController.java b/src/lightControlSoftware/src/view/group/editPanel/EditDeleteGroupController.java new file mode 100644 index 0000000..f5202bf --- /dev/null +++ b/src/lightControlSoftware/src/view/group/editPanel/EditDeleteGroupController.java @@ -0,0 +1,214 @@ +package view.group.editPanel; + +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.TextField; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.image.Image; + +import java.util.Optional; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; + +import javafx.scene.control.ListView; + +import javafx.scene.control.ComboBox; + +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; +import view.fixture.FixtureListCell; +import view.fixture.FixturesListItem; +import viewModel.KiwiViewModelHandler; +import viewModel.fixture.FixtureRepositoryVM; +import viewModel.fixture.FixtureVM; +import viewModel.group.GroupRepositoryVM; +import viewModel.group.GroupVM; + +public class EditDeleteGroupController { + @FXML + private BorderPane root; + @FXML + private ComboBox<String> comboboxGroup; + @FXML + private Button buttonDelete; + @FXML + private TextField textboxName; + @FXML + private ListView<FixturesListItem> groupList; + @FXML + private Button buttonCreateGroup; + @FXML + private Button buttonCancel; + + private FixtureRepositoryVM fixtureRepositoryVM; + private GroupRepositoryVM groupRepositoryVM; + private ObservableList<FixturesListItem> fixtures = FXCollections.observableArrayList(); + private ObservableList<String> groupNames = FXCollections.observableArrayList(); + private Stage stage; + private GroupVM groupVM; + + public void init(Stage stage, KiwiViewModelHandler viewModelHandler) { + this.stage = stage; + this.fixtureRepositoryVM = viewModelHandler.getFixtureRepositoryVM(); + this.groupRepositoryVM = viewModelHandler.getGroupRepositoryVM(); + this.loadGroups(); + this.loadFixtures(); + + this.fixtureRepositoryVM.fixtureListProperty().addListener((ListChangeListener.Change<? extends FixtureVM> change) -> { + while(change.next()) { + change.getAddedSubList().forEach(fixtureVM -> { + this.fixtures.add(new FixturesListItem(fixtureVM)); + }); + + change.getRemoved().forEach(fixtureVM -> { + int fixtureListIndex = -1; + for(int i = 0; i < this.fixtures.size(); i++) { + if(this.fixtures.get(i).getFixtureName().equals(fixtureVM.getFixtureName())) { + fixtureListIndex = i; + break; + } + } + if(fixtureListIndex != -1) { + this.fixtures.remove(fixtureListIndex); + } + }); + } + }); + + + this.groupList.setItems(this.fixtures); + this.groupList.setCellFactory(param -> new FixtureListCell()); + this.updateFixtures(); + } + + // Event Listener on ComboBox[#comboboxGroup].onAction + @FXML + public void selectGroup(ActionEvent event) { + if(!(this.comboboxGroup.getValue() == null)) { + this.groupVM = groupRepositoryVM.getGroupVM(this.comboboxGroup.getValue()); + this.textboxName.setText(this.groupVM.getGroupName()); + this.updateFixtures(); + } + } + // Event Listener on Button[#buttonDelete].onAction + @FXML + public void deleteGroup(ActionEvent event) { + Alert alert = new Alert(AlertType.CONFIRMATION); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Gruppe l�schen"); + alert.setHeaderText(String.format("Sind Sie sicher, dass Sie die Gruppe %s l�schen m�chten", this.groupVM.getGroupName())); + ButtonType buttonDelete = new ButtonType("Gruppe l�schen"); + ButtonType buttonCancel = new ButtonType("Abbrechen", ButtonData.CANCEL_CLOSE); + alert.getButtonTypes().setAll(buttonDelete, buttonCancel); + Optional<ButtonType> result = alert.showAndWait(); + if (result.orElseThrow() == buttonDelete) { + this.groupRepositoryVM.deleteGroup(this.textboxName.getText()); + this.loadGroups(); + } + } + // Event Listener on Button[#buttonCreateGroup].onAction + @FXML + public void createGroup(ActionEvent event) { + if(this.isDataValid()) { + this.groupVM.setGroupName(this.textboxName.getText()); + + this.fixtureRepositoryVM.fixtureListProperty().forEach(fixtureVM -> { + if(this.groupVM.isFixtureInGroup(fixtureVM.getFixtureName()) && !fixtureVM.isFixtureInGroup()) { + this.groupVM.removeFixture(fixtureVM); + } else if (!this.groupVM.isFixtureInGroup(fixtureVM.getFixtureName()) && fixtureVM.isFixtureInGroup()){ + this.groupVM.addFixture(fixtureVM); + } + }); + this.stage.close(); + } + + + } + // Event Listener on Button[#buttonCancel].onAction + @FXML + public void cancel(ActionEvent event) { + this.stage.close(); + } + + private boolean isDataValid() { + boolean isValid = false; + String headerText = ""; + String contentText = ""; + + if(this.textboxName.getText().isEmpty()) { + headerText = "Fehlerhafter Name."; + contentText = "Das Namensfeld darf nicht leer sein."; + } else if(nameAllreadyExist(this.textboxName.getText())) { + headerText = "Fehlerhafter Name."; + contentText = "Der Name ist bereits in Benutzung."; + } else { + isValid = true; + } + + if(!isValid) { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.showAndWait(); + } + + return isValid; + } + + private boolean nameAllreadyExist(String name) { + boolean nameExist = false; + if(!this.groupVM.getGroupName().equals(name) && + this.groupNames.stream() + .filter(groupName -> groupName.equals(name)) + .count() > 0){ + nameExist = true; + } + + return nameExist; + } + + private void loadGroups() { + this.groupNames.clear(); + this.groupRepositoryVM.groupsListProperty().forEach(groupVM -> { + this.groupNames.add(groupVM.getGroupName()); + }); + this.groupVM = groupRepositoryVM.getGroupVM(this.groupNames.get(0)); + this.comboboxGroup.setItems(this.groupNames); + this.comboboxGroup.setValue(this.groupNames.get(0)); + this.textboxName.setText(this.groupVM.getGroupName()); + } + + public void loadFixtures() { + this.fixtureRepositoryVM.fixturesRepositoryProperty().forEach(fixtureVM -> { + this.fixtures.add(new FixturesListItem(fixtureVM)); + }); + } + + public void updateFixtures() { + this.fixtureRepositoryVM.fixtureListProperty().forEach(fixtureVM -> { + if(this.groupVM.isFixtureInGroup(fixtureVM.getFixtureName())) { + fixtureVM.setFixtureInGroup(true); + } else { + fixtureVM.setFixtureInGroup(false); + } + }); + } + + public void setGroup(GroupVM groupVM) { + comboboxGroup.valueProperty().set(groupVM.getGroupName()); + this.groupVM = groupRepositoryVM.getGroupVM(this.comboboxGroup.getValue()); + this.textboxName.setText(this.groupVM.getGroupName()); + this.updateFixtures(); + } + +} diff --git a/src/lightControlSoftware/src/view/group/mainPanel/GroupMainPanel.fxml b/src/lightControlSoftware/src/view/group/mainPanel/GroupMainPanel.fxml new file mode 100644 index 0000000..cfeedec --- /dev/null +++ b/src/lightControlSoftware/src/view/group/mainPanel/GroupMainPanel.fxml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import java.lang.String?> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.text.Font?> + +<AnchorPane prefHeight="580.0" prefWidth="300.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.group.mainPanel.GroupMainPanelController"> + <children> + <AnchorPane layoutX="5.0" layoutY="7.0" styleClass="section" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0"> + <children> + <BorderPane AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <center> + <ScrollPane fitToHeight="true" fitToWidth="true"> + <content> + <ListView fx:id="groupsList" prefHeight="200.0" prefWidth="200.0"> + <styleClass> + <String fx:value="list-cell" /> + <String fx:value="white-background" /> + </styleClass></ListView> + </content> + </ScrollPane> + </center> + <top> + <Button fx:id="buttonCreateNewGroupe" mnemonicParsing="false" onAction="#createGroup" text="Neue Gruppe erstellen" BorderPane.alignment="CENTER_LEFT"> + <BorderPane.margin> + <Insets bottom="5.0" left="5.0" top="5.0" /> + </BorderPane.margin> + </Button> + </top> + </BorderPane> + </children> + <padding> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </padding> + </AnchorPane> + <Label layoutX="20.0" styleClass="section-headline" text="Gruppen" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="0.0"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Label> + </children> +</AnchorPane> diff --git a/src/lightControlSoftware/src/view/group/mainPanel/GroupMainPanelController.java b/src/lightControlSoftware/src/view/group/mainPanel/GroupMainPanelController.java new file mode 100644 index 0000000..45439ba --- /dev/null +++ b/src/lightControlSoftware/src/view/group/mainPanel/GroupMainPanelController.java @@ -0,0 +1,100 @@ +package view.group.mainPanel; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Stage; +import view.composition.mainPanel.CompositionMainPanelController; +import view.group.GroupListCell; +import view.group.GroupListItem; +import view.group.createPanel.CreateGroupController; +import view.lightingMood.mainPanel.LightingMoodMainPanelController; +import javafx.scene.control.Button; +import java.io.IOException; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.scene.control.ListView; +import viewModel.KiwiViewModelHandler; +import viewModel.group.GroupRepositoryVM; +import viewModel.group.GroupVM; + +public class GroupMainPanelController { + @FXML + private ListView<GroupListItem> groupsList; + @FXML + private Button buttonCreateNewGroupe; + + private ObservableList<GroupListItem> groups = FXCollections.observableArrayList(); + private GroupRepositoryVM groupRepositoryVM; + private KiwiViewModelHandler viewModelHandler; + private LightingMoodMainPanelController lightingMoodController; + private CompositionMainPanelController comositionController; + + public void init(KiwiViewModelHandler viewModelHandler, LightingMoodMainPanelController lightingMoodController, CompositionMainPanelController comositionController) { + this.viewModelHandler = viewModelHandler; + this.groupRepositoryVM = viewModelHandler.getGroupRepositoryVM(); + this.lightingMoodController = lightingMoodController; + this.comositionController = comositionController; + this.groupRepositoryVM.currentSelectedGroupProperty().addListener((object, oldValue, newValue) -> { + this.lightingMoodController.setLightingMoodRepository(newValue); + this.comositionController.setCompositionRepository(newValue); + }); + + + this.loadGroups(); + groupRepositoryVM.groupsListProperty().addListener((ListChangeListener.Change<? extends GroupVM> change) -> { + while(change.next()) { + change.getAddedSubList().forEach(group -> { + this.groups.add(new GroupListItem(group, this.viewModelHandler)); + }); + + change.getRemoved().forEach(group -> { + int groupListIndex = -1; + for(int i = 0; i < this.groups.size(); i++) { + if(this.groups.get(i).getGroupName().equals(group.getGroupName())) { + groupListIndex = i; + break; + } + } + if(groupListIndex != -1) { + this.groups.remove(groupListIndex); + } + }); + } + }); + + this.groupsList.setItems(this.groups); + this.groupsList.setCellFactory(param -> new GroupListCell()); + this.groupsList.getItems().get(0).selectedGroup(); + + this.lightingMoodController.setLightingMoodRepository(this.groupRepositoryVM.getGroupVM(this.groupsList.getItems().get(0).getGroupName())); + this.comositionController.setCompositionRepository(this.groupRepositoryVM.getGroupVM(this.groupsList.getItems().get(0).getGroupName())); + } + + public void loadGroups() { + this.groupRepositoryVM.groupsListProperty().forEach(group -> { + GroupListItem groupListItem = new GroupListItem(group, this.viewModelHandler); + this.groups.add(groupListItem); + }); + } + + // Event Listener on Button[#buttonCreateNewGroupe].onAction + @FXML + public void createGroup(ActionEvent event) throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../../group/createPanel/CreateGroup.fxml")); + Parent createGroupPanel = loader.load(); + CreateGroupController createGroupController = loader.getController(); + Scene scene = new Scene(createGroupPanel); + Stage groupDialog = new Stage(); + createGroupController.init(groupDialog, viewModelHandler); + groupDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + groupDialog.setScene(scene); + groupDialog.setTitle("Gruppe hinzuf�gen"); + groupDialog.showAndWait(); + } + +} diff --git a/src/lightControlSoftware/src/view/lightController/mainPanel/LightControllerMainPanel.fxml b/src/lightControlSoftware/src/view/lightController/mainPanel/LightControllerMainPanel.fxml new file mode 100644 index 0000000..643b25c --- /dev/null +++ b/src/lightControlSoftware/src/view/lightController/mainPanel/LightControllerMainPanel.fxml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import view.elements.ThreeStageSwitchButton?> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> + +<AnchorPane prefHeight="60.0" prefWidth="300.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.lightController.mainPanel.LightControllerMainPanelController"> + <children> + <AnchorPane prefHeight="60.0" prefWidth="254.0" styleClass="section" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="7.0"> + <children> + <VBox alignment="CENTER_RIGHT" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="0.0"> + <children> + <ThreeStageSwitchButton fx:id="connectSwitch" maxHeight="20.0" maxWidth="45.0" minHeight="20.0" minWidth="45.0" prefHeight="20.0" prefWidth="45.0" /> + </children> + </VBox> + <VBox alignment="CENTER_RIGHT" layoutX="10.0" layoutY="10.0" styleClass="lable-box" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="0.0"> + <children> + <Label fx:id="labelInterfaceConnection" text="Nicht verbunden"> + <padding> + <Insets left="5.0" right="5.0" /> + </padding> + </Label> + </children> + </VBox> + </children> + </AnchorPane> + <Label styleClass="section-headline" text="Interface" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="0.0"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Label> + </children> +</AnchorPane> diff --git a/src/lightControlSoftware/src/view/lightController/mainPanel/LightControllerMainPanelController.java b/src/lightControlSoftware/src/view/lightController/mainPanel/LightControllerMainPanelController.java new file mode 100644 index 0000000..1fe3cdf --- /dev/null +++ b/src/lightControlSoftware/src/view/lightController/mainPanel/LightControllerMainPanelController.java @@ -0,0 +1,64 @@ +package view.lightController.mainPanel; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import view.elements.ThreeStageButtonCallBack; +import view.elements.ThreeStageSwitchButton; +import viewModel.lightController.ArtNetControlleVM; + +public class LightControllerMainPanelController implements ThreeStageButtonCallBack { + @FXML + private Label labelInterfaceConnection; + + @FXML + private ThreeStageSwitchButton connectSwitch; + + private ArtNetControlleVM artNetControlleVM; + + public void init(ArtNetControlleVM artNetControlleVM) { + this.artNetControlleVM = artNetControlleVM; + this.connectSwitch.setCallback(this); + this.artNetControlleVM.getArtNetNodeRepositoryVM().connectStateProperty().bindBidirectional(this.connectSwitch.buttonStateProperty()); + this.connectSwitch.buttonStateProperty().addListener((object, oldValue, newValue) -> { + switch(newValue) { + case ON: + Platform.runLater(() -> { + this.labelInterfaceConnection.setText("Verbunden"); + }); + break; + case OFF: + Platform.runLater(() -> { + this.labelInterfaceConnection.setText("Nicht verbunden"); + }); + break; + default: + break; + } + }); + } + + @Override + public void callClicked() { + switch(this.connectSwitch.buttonStateProperty().get()) { + case OFF: + if(this.labelInterfaceConnection.getText().equals("Verbindung wird hergestellt...")) { + this.labelInterfaceConnection.setText("Verbindung wird beendet"); + this.artNetControlleVM.disconnectFromNodes(); + } else { + this.labelInterfaceConnection.setText("Verbindung wird hergestellt..."); + this.artNetControlleVM.connectToNodes(); + } + break; + case MID: + this.labelInterfaceConnection.setText("Verbindung wird beendet"); + this.artNetControlleVM.disconnectFromNodes(); + break; + case ON: + this.labelInterfaceConnection.setText("Verbindung wird beendet"); + this.artNetControlleVM.disconnectFromNodes(); + break; + } + + } +} diff --git a/src/lightControlSoftware/src/view/lightingMood/LightingMoodListItem.java b/src/lightControlSoftware/src/view/lightingMood/LightingMoodListItem.java new file mode 100644 index 0000000..f928213 --- /dev/null +++ b/src/lightControlSoftware/src/view/lightingMood/LightingMoodListItem.java @@ -0,0 +1,224 @@ +package view.lightingMood; + +import java.io.IOException; +import java.util.Optional; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import view.elements.ThreeStageSwitchButton; +import view.lightingMood.editDeletePanel.EditDeleteLightingMoodController; +import viewModel.KiwiViewModelHandler; +import viewModel.group.GroupVM; +import viewModel.group.GroupVM.State; +import viewModel.lightingMood.LightingMoodVM; + +public class LightingMoodListItem extends AnchorPane{ + + private LightingMoodVM lightingMoodVM; + private String lightingMoodName; + private BooleanProperty selected; + private GroupVM groupVM; + + public LightingMoodListItem(LightingMoodVM lightingMoodVM, GroupVM groupVM, KiwiViewModelHandler viewModelHandler) { + this.lightingMoodVM = lightingMoodVM; + this.groupVM = groupVM; + this.lightingMoodName = lightingMoodVM.getLightingMoodName(); + selected = new SimpleBooleanProperty(false); + + setMaxHeight(80.0); + setMinHeight(80.0); + setPrefHeight(80.0); + setMaxWidth(200.0); + setMinWidth(200.0); + setPrefWidth(200.0); + FlowPane.setMargin(this, new Insets(5.0, 5.0, 5.0, 5.0)); + + VBox vBox = new VBox(); + vBox.setAlignment(Pos.CENTER_LEFT); + vBox.setLayoutX(7.0); + vBox.setLayoutY(2.0); + vBox.setMaxWidth(100.0); + AnchorPane.setBottomAnchor(vBox, 1.0); + AnchorPane.setLeftAnchor(vBox, 5.0); + AnchorPane.setRightAnchor(vBox, 5.0); + AnchorPane.setTopAnchor(vBox, 1.0); + + Label lableLightingMoodName = new Label(lightingMoodName); + lableLightingMoodName.setAlignment(Pos.TOP_LEFT); + lableLightingMoodName.setMaxHeight(34.0); + lableLightingMoodName.setWrapText(true); + lableLightingMoodName.textProperty().bind(this.lightingMoodVM.LightingMoodNameProperty()); + VBox.setVgrow(lableLightingMoodName, Priority.ALWAYS); + VBox.setMargin(lableLightingMoodName, new Insets(5.0, 0.0, 0.0, 0.0)); + + AnchorPane buttonArea = new AnchorPane(); + + VBox vBoxSwitchButton = new VBox(); + vBoxSwitchButton.setAlignment(Pos.CENTER_LEFT); + vBoxSwitchButton.setLayoutX(-1.0); + vBoxSwitchButton.setLayoutY(-1.0); + AnchorPane.setBottomAnchor(vBoxSwitchButton, 0.0); + AnchorPane.setLeftAnchor(vBoxSwitchButton, 5.0); + AnchorPane.setTopAnchor(vBoxSwitchButton, 0.0); + + ThreeStageSwitchButton switchButton = new ThreeStageSwitchButton(); + switchButton.setMaxHeight(20.0); + switchButton.setMinHeight(20.0); + switchButton.setPrefHeight(20.0); + switchButton.setMaxWidth(45.0); + switchButton.setMinWidth(45.0); + switchButton.setPrefWidth(45.0); + + if(this.lightingMoodVM.LightingMoodIsActiveProperty().get()) { + switchButton.buttonStateProperty().set(State.ON); + } + + this.lightingMoodVM.LightingMoodIsActiveProperty().addListener((object, oldValue, newValue) -> { + if(newValue) { + if(switchButton.buttonStateProperty().get() != State.ON) { + //switchButton.buttonStateProperty().set(State.ON); + } + } else { + if(switchButton.buttonStateProperty().get() != State.OFF) { + switchButton.buttonStateProperty().set(State.OFF); + } + } + }); + + + switchButton.buttonStateProperty().addListener((object, oldValue, newValue) -> { + switch(newValue) { + case ON: + this.groupVM.activateLightingMood(this.lightingMoodVM); + break; + case OFF: + this.groupVM.deactivateLightingMood(this.lightingMoodVM); + break; + default: + break; + } + }); + + HBox hBox = new HBox(); + hBox.setAlignment(Pos.CENTER_RIGHT); + AnchorPane.setBottomAnchor(hBox, 0.0); + AnchorPane.setRightAnchor(hBox, 5.0); + AnchorPane.setTopAnchor(hBox, 0.0); + + Image icon1 = new Image("images/Edit.png"); + ImageView imageView1 = new ImageView(); + imageView1.setImage(icon1); + imageView1.setFitHeight(25.0); + imageView1.setFitWidth(25.0); + imageView1.setPickOnBounds(true); + imageView1.setPreserveRatio(true); + + + Button buttonEditGroup = new Button(); + buttonEditGroup.setMnemonicParsing(false); + buttonEditGroup.setStyle("-fx-background-color: none"); + buttonEditGroup.setOnAction(event -> { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("./editDeletePanel/EditDeleteLightingMood.fxml")); + Parent editDeleteLightingMoodPanel; + editDeleteLightingMoodPanel = loader.load(); + EditDeleteLightingMoodController editDeleteLightingMoodController = loader.getController(); + Scene scene = new Scene(editDeleteLightingMoodPanel); + Stage lightingMoodDialog = new Stage(); + editDeleteLightingMoodController.init(lightingMoodDialog, viewModelHandler); + editDeleteLightingMoodController.setGroup(this.groupVM.getGroupName()); + editDeleteLightingMoodController.setLightingMood(this.lightingMoodName); + lightingMoodDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + lightingMoodDialog.setScene(scene); + lightingMoodDialog.setTitle("Lichtstimmung bearbeiten/l�schen"); + lightingMoodDialog.showAndWait(); + } catch (IOException e) { + e.printStackTrace(); + } + }); + buttonEditGroup.setGraphic(imageView1); + + Image icon2 = new Image("images/Delete.png"); + ImageView imageView2 = new ImageView(); + imageView2.setImage(icon2); + imageView2.setFitHeight(25.0); + imageView2.setFitWidth(25.0); + imageView2.setPickOnBounds(true); + imageView2.setPreserveRatio(true); + + + Button buttonDeleteGroup = new Button(); + buttonDeleteGroup.setMnemonicParsing(false); + buttonDeleteGroup.setStyle("-fx-background-color: none"); + buttonDeleteGroup.setOnAction(event -> { + Alert alert = new Alert(AlertType.CONFIRMATION); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Lichtstimmung l�schen"); + alert.setHeaderText(String.format("Sind Sie sicher, dass Sie die Lichtstimmung %s l�schen m�chten", this.getLightingMoodName())); + ButtonType buttonDelete = new ButtonType("Lichstimmung l�schen"); + ButtonType buttonCancel = new ButtonType("Abbrechen", ButtonData.CANCEL_CLOSE); + alert.getButtonTypes().setAll(buttonDelete, buttonCancel); + Optional<ButtonType> result = alert.showAndWait(); + if (result.orElseThrow() == buttonDelete) { + this.groupVM.removeLightingMood(this.lightingMoodVM); + } + }); + buttonDeleteGroup.setGraphic(imageView2); + + + vBoxSwitchButton.getChildren().add(switchButton); + hBox.getChildren().addAll(buttonEditGroup, buttonDeleteGroup); + buttonArea.getChildren().addAll(vBoxSwitchButton, hBox); + vBox.getChildren().addAll(lableLightingMoodName, buttonArea); + getChildren().addAll(vBox); + getStyleClass().addAll("list-item", "item-diselected"); + + this.selected.bind(this.lightingMoodVM.LightingMoodIsSelectedProperty()); + + this.selected.addListener((object, oldValue, newValue) -> { + if(newValue) { + this.getStyleClass().add("item-selected"); + this.getStyleClass().remove("item-diselected"); + } else { + this.getStyleClass().add("item-diselected"); + this.getStyleClass().remove("item-selected"); + } + }); + + this.setOnMouseClicked(event -> { + this.lightingMoodVM.setLightingMoodIsSelected(true); + }); + } + + public boolean isSelected() { + return selected.get(); + } + + public BooleanProperty selectedProperty() { + return this.selected; + } + + public String getLightingMoodName() { + return this.lightingMoodName; + } +} diff --git a/src/lightControlSoftware/src/view/lightingMood/createPanel/CreateLightingMood.fxml b/src/lightControlSoftware/src/view/lightingMood/createPanel/CreateLightingMood.fxml new file mode 100644 index 0000000..b092a83 --- /dev/null +++ b/src/lightControlSoftware/src/view/lightingMood/createPanel/CreateLightingMood.fxml @@ -0,0 +1,194 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ColorPicker?> +<?import javafx.scene.control.ComboBox?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.Separator?> +<?import javafx.scene.control.Slider?> +<?import javafx.scene.control.Spinner?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> + +<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="350.0" minWidth="500.0" prefHeight="350.0" prefWidth="500.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.lightingMood.createPanel.CreateLightingMoodController"> + <center> + <AnchorPane BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="5.0"> + <children> + <BorderPane HBox.hgrow="ALWAYS"> + <HBox.margin> + <Insets /> + </HBox.margin> + <center> + <Slider fx:id="sliderMainIntensity" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" orientation="VERTICAL" showTickLabels="true" showTickMarks="true" value="255.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets bottom="10.0" top="5.0" /> + </BorderPane.margin> + </Slider> + </center> + <top> + <Label text="Dimmer" BorderPane.alignment="TOP_CENTER"> + <BorderPane.margin> + <Insets top="5.0" /> + </BorderPane.margin> + </Label> + </top> + <bottom> + <Spinner fx:id="spinnerMainIntensity" editable="true" maxWidth="65.0" BorderPane.alignment="BOTTOM_CENTER" /> + </bottom> + </BorderPane> + <BorderPane HBox.hgrow="ALWAYS"> + <center> + <Slider fx:id="sliderStrope" majorTickUnit="50.0" max="255.0" minorTickCount="5" orientation="VERTICAL" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets bottom="10.0" top="5.0" /> + </BorderPane.margin> + </Slider> + </center> + <top> + <Label text="Strobe" BorderPane.alignment="TOP_CENTER"> + <BorderPane.margin> + <Insets top="5.0" /> + </BorderPane.margin> + </Label> + </top> + <bottom> + <Spinner fx:id="spinnerStrope" editable="true" maxWidth="65.0" BorderPane.alignment="BOTTOM_CENTER" /> + </bottom> + <HBox.margin> + <Insets left="15.0" /> + </HBox.margin> + </BorderPane> + <BorderPane HBox.hgrow="ALWAYS"> + <top> + <HBox alignment="CENTER" BorderPane.alignment="CENTER"> + <children> + <Label text="Farbe" /> + <ColorPicker fx:id="colorPicker"> + <HBox.margin> + <Insets left="10.0" /> + </HBox.margin> + </ColorPicker> + </children> + <opaqueInsets> + <Insets /> + </opaqueInsets> + <BorderPane.margin> + <Insets left="5.0" /> + </BorderPane.margin> + </HBox> + </top> + <center> + <VBox BorderPane.alignment="CENTER"> + <children> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderRed" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" value="255.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="30.0" text="Rot" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerRed" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderGreen" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" value="255.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="30.0" text="Grün" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerGreen" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderBlue" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" value="255.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="30.0" text="Blau" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerBlue" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + </children> + <BorderPane.margin> + <Insets top="10.0" /> + </BorderPane.margin> + </VBox> + </center> + <HBox.margin> + <Insets left="20.0" /> + </HBox.margin> + </BorderPane> + </children> + </HBox> + </children> + </AnchorPane> + </center> + <top> + <VBox BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER_LEFT" VBox.vgrow="ALWAYS"> + <VBox.margin> + <Insets bottom="5.0" left="10.0" right="10.0" top="10.0" /> + </VBox.margin> + <children> + <HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS"> + <children> + <Label minWidth="65.0" text="Gruppe" /> + <ComboBox fx:id="comboBoxGroup" minWidth="150.0" HBox.hgrow="ALWAYS" /> + </children> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin> + </HBox> + <Separator orientation="VERTICAL" HBox.hgrow="ALWAYS" /> + <HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS"> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Bezeichnung*" /> + <TextField fx:id="textfieldName" HBox.hgrow="ALWAYS" /> + </children> + </HBox> + </children> + </HBox> + </children> + </VBox> + </top> + <bottom> + <HBox alignment="CENTER" BorderPane.alignment="CENTER"> + <children> + <Button fx:id="buttonCreateLightingMood" mnemonicParsing="false" onAction="#createLightingMood" text="Lichtstimmung hinzufügen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + <Button fx:id="buttonCancel" mnemonicParsing="false" onAction="#cancel" text="Abbrechen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + </children> + </HBox> + </bottom> +</BorderPane> diff --git a/src/lightControlSoftware/src/view/lightingMood/createPanel/CreateLightingMoodController.java b/src/lightControlSoftware/src/view/lightingMood/createPanel/CreateLightingMoodController.java new file mode 100644 index 0000000..f9a4db6 --- /dev/null +++ b/src/lightControlSoftware/src/view/lightingMood/createPanel/CreateLightingMoodController.java @@ -0,0 +1,211 @@ +package view.lightingMood.createPanel; + +import javafx.fxml.FXML; +import javafx.scene.control.ColorPicker; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.TextField; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.image.Image; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.scene.control.Slider; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.layout.BorderPane; +import javafx.scene.paint.Color; +import javafx.stage.Stage; +import model.lightingScene.lightingMood.LightingMood; +import viewModel.group.GroupRepositoryVM; +import viewModel.lightingMood.LightingMoodVM; + +public class CreateLightingMoodController { + @FXML + private BorderPane root; + @FXML + private Slider sliderMainIntensity; + @FXML + private Spinner<Integer> spinnerMainIntensity; + @FXML + private Slider sliderStrope; + @FXML + private Spinner<Integer> spinnerStrope; + @FXML + private ColorPicker colorPicker; + @FXML + private Slider sliderRed; + @FXML + private Spinner<Integer> spinnerRed; + @FXML + private Slider sliderGreen; + @FXML + private Spinner<Integer> spinnerGreen; + @FXML + private Slider sliderBlue; + @FXML + private Spinner<Integer> spinnerBlue; + @FXML + private ComboBox<String> comboBoxGroup; + @FXML + private TextField textfieldName; + @FXML + private Button buttonCreateLightingMood; + @FXML + private Button buttonCancel; + + private GroupRepositoryVM groupRepositoryVM; + private ObservableList<String> groupNames = FXCollections.observableArrayList(); + private Stage stage; + + + public void init(Stage stage, GroupRepositoryVM groupRepositoryVM) { + this.stage = stage; + this.groupRepositoryVM = groupRepositoryVM; + this.spinnerMainIntensity.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 255)); + this.spinnerStrope.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.spinnerRed.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 255)); + this.spinnerGreen.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 255)); + this.spinnerBlue.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 255)); + this.addListener(); + this.loadGroups(); + } + + public void setGroup(String groupName) { + this.comboBoxGroup.setValue(groupName); + } + + // Event Listener on Button[#buttonCreateLightingMood].onAction + @FXML + public void createLightingMood(ActionEvent event) { + if(this.isDataValid()) { + + try { + LightingMoodVM lightingMoodVM = new LightingMoodVM(new LightingMood(this.textfieldName.getText())); + lightingMoodVM.setLightingMoodMainIntensity(this.sliderMainIntensity.valueProperty().intValue()); + lightingMoodVM.setLightingMoodStrope(this.sliderStrope.valueProperty().intValue()); + lightingMoodVM.setLightingMoodRed(this.sliderRed.valueProperty().intValue()); + lightingMoodVM.setLightingMoodGreen(this.sliderGreen.valueProperty().intValue()); + lightingMoodVM.setLightingMoodBlue(this.sliderBlue.valueProperty().intValue()); + + this.groupRepositoryVM + .getGroupVM(this.comboBoxGroup.getValue()) + .addLightingMood(lightingMoodVM); + this.stage.close(); + } catch (IllegalArgumentException e) { + Alert alert = new Alert(AlertType.ERROR); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Fehler"); + alert.setHeaderText("Fehler beim Erstellen einer Lichtstimmung."); + alert.setContentText(e.getMessage()); + alert.showAndWait(); + } + } + } + // Event Listener on Button[#buttonCancel].onAction + @FXML + public void cancel(ActionEvent event) { + this.stage.close(); + } + + private boolean isDataValid() { + boolean isValid = false; + String headerText = ""; + String contentText = ""; + + if(this.textfieldName.getText().isEmpty()) { + headerText = "Fehlerhafter Name."; + contentText = "Das Namensfeld darf nicht leer sein."; + } else { + isValid = true; + } + + if(!isValid) { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.showAndWait(); + } + + return isValid; + } + + private void addListener() { + this.addSpinnerListener(this.spinnerMainIntensity, this.sliderMainIntensity); + this.addSpinnerListener(this.spinnerStrope, this.sliderStrope); + this.addSpinnerListener(this.spinnerRed, this.sliderRed); + this.addSpinnerListener(this.spinnerGreen, this.sliderGreen); + this.addSpinnerListener(this.spinnerBlue, this.sliderBlue); + + this.addSliderListener(this.spinnerMainIntensity, this.sliderMainIntensity); + this.addSliderListener(this.spinnerStrope, this.sliderStrope); + this.addSliderListener(this.spinnerRed, this.sliderRed); + this.addSliderListener(this.spinnerGreen, this.sliderGreen); + this.addSliderListener(this.spinnerBlue, this.sliderBlue); + + this.colorPicker.valueProperty().addListener((onject, oldValue, newValue) -> { + this.sliderRed.setValue(newValue.getRed()*255); + this.sliderGreen.setValue(newValue.getGreen()*255); + this.sliderBlue.setValue(newValue.getBlue()*255); + }); + + this.sliderRed.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + (double)newValue/255, + this.sliderGreen.getValue()/255, + this.sliderBlue.getValue()/255, 1 + )); + }); + + this.sliderGreen.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + this.sliderRed.getValue()/255, + (double)newValue/255, + this.sliderBlue.getValue()/255, 1 + )); + }); + + this.sliderBlue.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + this.sliderRed.getValue()/255, + this.sliderGreen.getValue()/255, + (double)newValue/255, 1 + )); + }); + } + + private void addSpinnerListener (Spinner<Integer> spinner, Slider slider) { + spinner.getValueFactory().valueProperty().addListener((object, oldValue, newValue) -> { + slider.valueProperty().set(newValue.doubleValue()); + }); + } + + private void addSliderListener (Spinner<Integer> spinner, Slider slider) { + slider.valueProperty().addListener((object, oldValue, newValue) -> { + spinner.getValueFactory().setValue(newValue.intValue()); + }); + } + + private void loadGroups() { + this.groupNames.clear(); + this.groupRepositoryVM.groupsListProperty().forEach(groupVM -> { + this.groupNames.add(groupVM.getGroupName()); + }); + this.comboBoxGroup.setItems(this.groupNames); + this.comboBoxGroup.setValue(this.groupNames.get(0)); + } +} diff --git a/src/lightControlSoftware/src/view/lightingMood/editDeletePanel/EditDeleteLightingMood.fxml b/src/lightControlSoftware/src/view/lightingMood/editDeletePanel/EditDeleteLightingMood.fxml new file mode 100644 index 0000000..9ead1aa --- /dev/null +++ b/src/lightControlSoftware/src/view/lightingMood/editDeletePanel/EditDeleteLightingMood.fxml @@ -0,0 +1,227 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ColorPicker?> +<?import javafx.scene.control.ComboBox?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.Separator?> +<?import javafx.scene.control.Slider?> +<?import javafx.scene.control.Spinner?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> + +<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="350.0" minWidth="550.0" prefHeight="350.0" prefWidth="550.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.lightingMood.editDeletePanel.EditDeleteLightingMoodController"> + <center> + <AnchorPane BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="5.0"> + <children> + <BorderPane HBox.hgrow="ALWAYS"> + <HBox.margin> + <Insets /> + </HBox.margin> + <center> + <Slider fx:id="sliderMainIntensity" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" orientation="VERTICAL" showTickLabels="true" showTickMarks="true" value="255.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets bottom="10.0" top="5.0" /> + </BorderPane.margin> + </Slider> + </center> + <top> + <Label text="Dimmer" BorderPane.alignment="TOP_CENTER"> + <BorderPane.margin> + <Insets top="5.0" /> + </BorderPane.margin> + </Label> + </top> + <bottom> + <Spinner fx:id="spinnerMainIntensity" editable="true" maxWidth="65.0" BorderPane.alignment="BOTTOM_CENTER" /> + </bottom> + </BorderPane> + <BorderPane HBox.hgrow="ALWAYS"> + <center> + <Slider fx:id="sliderStrope" majorTickUnit="50.0" max="255.0" minorTickCount="5" orientation="VERTICAL" showTickLabels="true" showTickMarks="true" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets bottom="10.0" top="5.0" /> + </BorderPane.margin> + </Slider> + </center> + <top> + <Label text="Strobe" BorderPane.alignment="TOP_CENTER"> + <BorderPane.margin> + <Insets top="5.0" /> + </BorderPane.margin> + </Label> + </top> + <bottom> + <Spinner fx:id="spinnerStrope" editable="true" maxWidth="65.0" BorderPane.alignment="BOTTOM_CENTER" /> + </bottom> + <HBox.margin> + <Insets left="15.0" /> + </HBox.margin> + </BorderPane> + <BorderPane HBox.hgrow="ALWAYS"> + <top> + <HBox alignment="CENTER" BorderPane.alignment="CENTER"> + <children> + <Label text="Farbe" /> + <ColorPicker fx:id="colorPicker"> + <HBox.margin> + <Insets left="10.0" /> + </HBox.margin> + </ColorPicker> + </children> + <opaqueInsets> + <Insets /> + </opaqueInsets> + <BorderPane.margin> + <Insets left="5.0" /> + </BorderPane.margin> + </HBox> + </top> + <center> + <VBox BorderPane.alignment="CENTER"> + <children> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderRed" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" value="255.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="30.0" text="Rot" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerRed" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderGreen" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" value="255.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="30.0" text="Grün" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerGreen" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + <BorderPane VBox.vgrow="ALWAYS"> + <center> + <Slider fx:id="sliderBlue" blockIncrement="5.0" majorTickUnit="50.0" max="255.0" minorTickCount="5" showTickLabels="true" showTickMarks="true" value="255.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets left="10.0" right="10.0" /> + </BorderPane.margin> + </Slider> + </center> + <left> + <Label minWidth="30.0" text="Blau" BorderPane.alignment="CENTER" /> + </left> + <right> + <Spinner fx:id="spinnerBlue" editable="true" maxWidth="65.0" BorderPane.alignment="CENTER" /> + </right> + </BorderPane> + </children> + <BorderPane.margin> + <Insets top="10.0" /> + </BorderPane.margin> + </VBox> + </center> + <HBox.margin> + <Insets left="20.0" /> + </HBox.margin> + </BorderPane> + </children> + </HBox> + </children> + </AnchorPane> + </center> + <top> + <VBox BorderPane.alignment="CENTER"> + <children> + <HBox alignment="CENTER_LEFT" VBox.vgrow="ALWAYS"> + <children> + <HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS"> + <children> + <Label minWidth="45.0" text="Gruppe" HBox.hgrow="NEVER" /> + <ComboBox fx:id="comboBoxGroup" minWidth="150.0" onAction="#selectGroup" HBox.hgrow="ALWAYS" /> + </children> + <HBox.margin> + <Insets right="10.0" /> + </HBox.margin> + </HBox> + <Separator orientation="VERTICAL" HBox.hgrow="ALWAYS" /> + <HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS"> + <children> + <Label minWidth="85.0" text="Lichtstimmung" HBox.hgrow="NEVER" /> + <ComboBox fx:id="comboBoxLightingMood" minWidth="150.0" onAction="#selectLightingMood" HBox.hgrow="ALWAYS" /> + </children> + <HBox.margin> + <Insets left="20.0" right="5.0" /> + </HBox.margin> + </HBox> + <Button fx:id="buttonDelete" mnemonicParsing="false" onAction="#deleteLightingMood" style="-fx-background-color: none;" HBox.hgrow="ALWAYS"> + <graphic> + <ImageView fitHeight="20.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@../../../../ressourcen/images/Delete.png" /> + </image> + </ImageView> + </graphic> + </Button> + </children> + <VBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </VBox.margin> + </HBox> + <HBox alignment="CENTER_LEFT" VBox.vgrow="ALWAYS"> + <VBox.margin> + <Insets bottom="5.0" left="10.0" right="10.0" top="5.0" /> + </VBox.margin> + <children> + <HBox alignment="CENTER_LEFT"> + <children> + <Label maxWidth="75.0" minWidth="75.0" prefWidth="75.0" text="Bezeichnung*"> + <HBox.margin> + <Insets right="5.0" /> + </HBox.margin></Label> + <TextField fx:id="textfieldName" /> + </children> + <HBox.margin> + <Insets right="10.0" /> + </HBox.margin> + </HBox> + </children> + </HBox> + </children> + </VBox> + </top> + <bottom> + <HBox alignment="CENTER" BorderPane.alignment="CENTER"> + <children> + <Button fx:id="buttonSave" mnemonicParsing="false" onAction="#saveLightingMood" text="Speichern"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + <Button fx:id="buttonCancel" mnemonicParsing="false" onAction="#cancel" text="Abbrechen"> + <HBox.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </HBox.margin> + </Button> + </children> + </HBox> + </bottom> +</BorderPane> diff --git a/src/lightControlSoftware/src/view/lightingMood/editDeletePanel/EditDeleteLightingMoodController.java b/src/lightControlSoftware/src/view/lightingMood/editDeletePanel/EditDeleteLightingMoodController.java new file mode 100644 index 0000000..81a0cdd --- /dev/null +++ b/src/lightControlSoftware/src/view/lightingMood/editDeletePanel/EditDeleteLightingMoodController.java @@ -0,0 +1,299 @@ +package view.lightingMood.editDeletePanel; + +import javafx.fxml.FXML; + +import javafx.scene.control.ColorPicker; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.TextField; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.image.Image; + +import java.util.Optional; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; + +import javafx.scene.control.Slider; + +import javafx.scene.control.ComboBox; + +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.layout.BorderPane; +import javafx.scene.paint.Color; +import javafx.stage.Stage; +import model.lightingScene.lightingMood.LightingMood; +import viewModel.KiwiViewModelHandler; +import viewModel.group.GroupRepositoryVM; +import viewModel.group.GroupVM; +import viewModel.lightingMood.LightingMoodRepositoryVM; +import viewModel.lightingMood.LightingMoodVM; + +public class EditDeleteLightingMoodController { + @FXML + private BorderPane root; + @FXML + private Slider sliderMainIntensity; + @FXML + private Spinner<Integer> spinnerMainIntensity; + @FXML + private Slider sliderStrope; + @FXML + private Spinner<Integer> spinnerStrope; + @FXML + private ColorPicker colorPicker; + @FXML + private Slider sliderRed; + @FXML + private Spinner<Integer> spinnerRed; + @FXML + private Slider sliderGreen; + @FXML + private Spinner<Integer> spinnerGreen; + @FXML + private Slider sliderBlue; + @FXML + private Spinner<Integer> spinnerBlue; + @FXML + private ComboBox<String> comboBoxLightingMood; + @FXML + private ComboBox<String> comboBoxGroup; + @FXML + private TextField textfieldName; + @FXML + private Button buttonSave; + @FXML + private Button buttonCancel; + @FXML + private Button buttonDelete; + + private Stage stage; + private ObservableList<String> groupNames = FXCollections.observableArrayList(); + private ObservableList<String> lightingMoodNames = FXCollections.observableArrayList(); + private GroupRepositoryVM groupRepositoryVM; + private LightingMoodRepositoryVM lightingMoodRepositoryVM; + private GroupVM groupVM; + private LightingMoodVM lightingMoodVM; + + public void init(Stage stage, KiwiViewModelHandler viewModelHandler) { + this.stage = stage; + this.groupRepositoryVM = viewModelHandler.getGroupRepositoryVM(); + this.spinnerMainIntensity.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 255)); + this.spinnerStrope.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 0)); + this.spinnerRed.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 255)); + this.spinnerGreen.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 255)); + this.spinnerBlue.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 255, 255)); + this.addListener(); + this.loadGroups(); + } + + // Event Listener on ComboBox[#comboBoxGroup].onAction + @FXML + public void selectGroup(ActionEvent event) { + if(!(this.comboBoxGroup.getValue() == null)) { + this.groupVM = groupRepositoryVM.getGroupVM(this.comboBoxGroup.getValue()); + this.loadLightingMoods(); + } + } + + // Event Listener on ComboBox[#comboBoxLightingMood].onAction + @FXML + public void selectLightingMood(ActionEvent event) { + if(!(this.comboBoxLightingMood.getValue() == null)) { + this.lightingMoodVM = lightingMoodRepositoryVM.getLightingMood(this.comboBoxLightingMood.getValue()); + this.updateLightingMood(); + } + } + + // Event Listener on Button[#buttonDelete].onAction + public void deleteLightingMood(ActionEvent event) { + Alert alert = new Alert(AlertType.CONFIRMATION); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Lichtstimmung l�schen"); + alert.setHeaderText(String.format("Sind Sie sicher, dass Sie die Lichtstimmung %s l�schen m�chten", this.lightingMoodVM.getLightingMoodName())); + ButtonType buttonDelete = new ButtonType("Lichtstimmung l�schen"); + ButtonType buttonCancel = new ButtonType("Abbrechen", ButtonData.CANCEL_CLOSE); + alert.getButtonTypes().setAll(buttonDelete, buttonCancel); + Optional<ButtonType> result = alert.showAndWait(); + if (result.orElseThrow() == buttonDelete) { + this.lightingMoodRepositoryVM.removeLightingMood(lightingMoodVM); + this.loadLightingMoods(); + } + } + + // Event Listener on Button[#buttonSave].onAction + @FXML + public void saveLightingMood(ActionEvent event) { + if(isDataValid()) { + this.lightingMoodVM.setLightingMoodName(this.textfieldName.getText()); + this.lightingMoodVM.setLightingMoodMainIntensity(this.sliderMainIntensity.valueProperty().getValue().intValue()); + this.lightingMoodVM.setLightingMoodStrope(this.sliderStrope.valueProperty().getValue().intValue()); + this.lightingMoodVM.setLightingMoodRed(this.sliderRed.valueProperty().getValue().intValue()); + this.lightingMoodVM.setLightingMoodGreen(this.sliderGreen.valueProperty().getValue().intValue()); + this.lightingMoodVM.setLightingMoodBlue(this.sliderBlue.valueProperty().getValue().intValue()); + this.stage.close(); + } + } + + // Event Listener on Button[#buttonCancel].onAction + @FXML + public void cancel(ActionEvent event) { + this.stage.close(); + } + + private void addListener() { + this.addSpinnerListener(this.spinnerMainIntensity, this.sliderMainIntensity); + this.addSpinnerListener(this.spinnerStrope, this.sliderStrope); + this.addSpinnerListener(this.spinnerRed, this.sliderRed); + this.addSpinnerListener(this.spinnerGreen, this.sliderGreen); + this.addSpinnerListener(this.spinnerBlue, this.sliderBlue); + + this.addSliderListener(this.spinnerMainIntensity, this.sliderMainIntensity); + this.addSliderListener(this.spinnerStrope, this.sliderStrope); + this.addSliderListener(this.spinnerRed, this.sliderRed); + this.addSliderListener(this.spinnerGreen, this.sliderGreen); + this.addSliderListener(this.spinnerBlue, this.sliderBlue); + + this.colorPicker.valueProperty().addListener((onject, oldValue, newValue) -> { + this.sliderRed.setValue(newValue.getRed()*255); + this.sliderGreen.setValue(newValue.getGreen()*255); + this.sliderBlue.setValue(newValue.getBlue()*255); + }); + + this.sliderRed.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + (double)newValue/255, + this.sliderGreen.getValue()/255, + this.sliderBlue.getValue()/255, 1 + )); + }); + + this.sliderGreen.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + this.sliderRed.getValue()/255, + (double)newValue/255, + this.sliderBlue.getValue()/255, 1 + )); + }); + + this.sliderBlue.valueProperty().addListener((object, oldValue, newValue) -> { + this.colorPicker + .valueProperty() + .setValue( + new Color( + this.sliderRed.getValue()/255, + this.sliderGreen.getValue()/255, + (double)newValue/255, 1 + )); + }); + } + + private void addSpinnerListener (Spinner<Integer> spinner, Slider slider) { + spinner.getValueFactory().valueProperty().addListener((object, oldValue, newValue) -> { + slider.valueProperty().set(newValue.doubleValue()); + }); + } + + private void addSliderListener (Spinner<Integer> spinner, Slider slider) { + slider.valueProperty().addListener((object, oldValue, newValue) -> { + spinner.getValueFactory().setValue(newValue.intValue()); + }); + } + + private void loadGroups() { + this.groupNames.clear(); + this.groupRepositoryVM.groupsListProperty().forEach(groupVM -> { + this.groupNames.add(groupVM.getGroupName()); + }); + this.groupVM = groupRepositoryVM.getGroupVM(this.groupNames.get(0)); + this.comboBoxGroup.setItems(this.groupNames); + this.comboBoxGroup.setValue(this.groupNames.get(0)); + this.loadLightingMoods(); + } + + private void loadLightingMoods() { + this.lightingMoodNames.clear(); + this.lightingMoodRepositoryVM = groupVM.getLightingMoodRepositoryVM(); + this.lightingMoodRepositoryVM.lightingMoodListProperty().forEach(lightingMoodVM -> { + this.lightingMoodNames.add(lightingMoodVM.getLightingMoodName()); + }); + this.comboBoxLightingMood.setItems(this.lightingMoodNames); + if(lightingMoodRepositoryVM.lightingMoodListProperty().size() > 0) { + this.lightingMoodVM = lightingMoodRepositoryVM.getLightingMood(this.lightingMoodNames.get(0)); + this.comboBoxLightingMood.setValue(this.lightingMoodNames.get(0)); + } else { + this.lightingMoodVM = new LightingMoodVM(new LightingMood("Lichtstimmung")); + } + this.updateLightingMood(); + } + + public void updateLightingMood() { + this.textfieldName.setText(this.lightingMoodVM.getLightingMoodName()); + this.sliderMainIntensity.valueProperty().set(this.lightingMoodVM.getLightingMoodMainIntensity()); + this.sliderStrope.valueProperty().set(this.lightingMoodVM.getLightingMoodStrope()); + this.sliderRed.valueProperty().set(this.lightingMoodVM.getLightingMoodRed()); + this.sliderGreen.valueProperty().set(this.lightingMoodVM.getLightingMoodGreen()); + this.sliderBlue.valueProperty().set(this.lightingMoodVM.getLightingMoodBlue()); + } + + private boolean isDataValid() { + boolean isValid = false; + String headerText = ""; + String contentText = ""; + + if(this.textfieldName.getText().isEmpty()) { + headerText = "Fehlerhafter Name."; + contentText = "Das Namensfeld darf nicht leer sein."; + } else if(this.nameAllreadyExist(this.textfieldName.getText())) { + headerText = "Fehlerhafter Name."; + contentText = "Der Name ist bereits in Benutzung."; + } else { + isValid = true; + } + + if(!isValid) { + Alert alert = new Alert(AlertType.WARNING); + alert.setTitle("Warnung"); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.showAndWait(); + } + + return isValid; + } + + private boolean nameAllreadyExist(String name) { + boolean nameExist = false; + if(!this.lightingMoodVM.getLightingMoodName().equals(name) && + this.lightingMoodNames.stream() + .filter(lightingMoodName -> lightingMoodName.equals(name)) + .count() > 0){ + nameExist = true; + } + + return nameExist; + } + + public void setGroup(String groupName) { + this.comboBoxGroup.valueProperty().set(groupName); + this.groupVM = groupRepositoryVM.getGroupVM(this.comboBoxGroup.getValue()); + this.loadLightingMoods(); + } + + public void setLightingMood(String lightingMoodName) { + this.comboBoxLightingMood.valueProperty().set(lightingMoodName); + this.lightingMoodVM = lightingMoodRepositoryVM.getLightingMood(this.comboBoxLightingMood.getValue()); + this.updateLightingMood(); + } +} diff --git a/src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanel.fxml b/src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanel.fxml new file mode 100644 index 0000000..6de82c3 --- /dev/null +++ b/src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanel.fxml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.FlowPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.text.Font?> + +<AnchorPane prefHeight="350.0" prefWidth="880.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.lightingMood.mainPanel.LightingMoodMainPanelController"> + <children> + <AnchorPane layoutX="5.0" layoutY="7.0" styleClass="section" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0"> + <children> + <HBox layoutX="7.0" layoutY="12.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <children> + <BorderPane HBox.hgrow="ALWAYS"> + <center> + <ScrollPane fitToHeight="true" fitToWidth="true"> + <content> + <FlowPane fx:id="flowPaneLightingMoodList" /> + </content> + </ScrollPane> + </center> + <top> + <Button fx:id="buttonCreateNewLightingMood" mnemonicParsing="false" onAction="#createLightingMood" text="Neue Lichtstimmung erstellen" BorderPane.alignment="CENTER_LEFT"> + <BorderPane.margin> + <Insets bottom="5.0" left="5.0" top="5.0" /> + </BorderPane.margin> + </Button> + </top> + </BorderPane> + </children> + </HBox> + </children> + <padding> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </padding> + </AnchorPane> + <Label layoutX="20.0" styleClass="section-headline" text="Lichtstimmung" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="0.0"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Label> + </children> +</AnchorPane> diff --git a/src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanel2.fxml b/src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanel2.fxml new file mode 100644 index 0000000..5eead5d --- /dev/null +++ b/src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanel2.fxml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ColorPicker?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.control.Separator?> +<?import javafx.scene.control.Slider?> +<?import javafx.scene.control.Spinner?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.FlowPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> + +<AnchorPane prefHeight="350.0" prefWidth="880.0" stylesheets="@../../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.lightingMood.mainPanel.LightingMoodMainPanelController"> + <children> + <AnchorPane layoutX="5.0" layoutY="7.0" styleClass="section" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0"> + <children> + <HBox layoutX="7.0" layoutY="12.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <children> + <BorderPane HBox.hgrow="ALWAYS"> + <center> + <ScrollPane fitToHeight="true" fitToWidth="true"> + <content> + <FlowPane fx:id="flowPaneLightingMoodList" /> + </content> + </ScrollPane> + </center> + <top> + <Button fx:id="buttonCreateNewLightingMood" mnemonicParsing="false" onAction="#createLightingMood" text="Neue Lichtstimmung erstellen" BorderPane.alignment="CENTER_LEFT"> + <BorderPane.margin> + <Insets bottom="5.0" left="5.0" top="5.0" /> + </BorderPane.margin> + </Button> + </top> + </BorderPane> + <Separator orientation="VERTICAL" prefHeight="200.0" /> + <VBox alignment="CENTER" HBox.hgrow="NEVER"> + <children> + <ColorPicker fx:id="colorPicker" VBox.vgrow="ALWAYS"> + <VBox.margin> + <Insets bottom="10.0" /> + </VBox.margin> + </ColorPicker> + <HBox alignment="CENTER"> + <children> + <VBox alignment="CENTER"> + <children> + <Label text="Dimmer"> + <VBox.margin> + <Insets bottom="5.0" /> + </VBox.margin> + </Label> + <Slider fx:id="faderIntensity" maxHeight="150.0" minHeight="150.0" orientation="VERTICAL" prefHeight="150.0"> + <VBox.margin> + <Insets bottom="5.0" top="5.0" /> + </VBox.margin> + </Slider> + <Spinner fx:id="spinnerIntensity" maxHeight="20.0" minHeight="20.0" prefHeight="20.0" prefWidth="60.0"> + <VBox.margin> + <Insets top="5.0" /> + </VBox.margin> + </Spinner> + </children> + <HBox.margin> + <Insets left="10.0" right="10.0" /> + </HBox.margin> + </VBox> + <VBox alignment="CENTER"> + <children> + <Label text="Strope"> + <VBox.margin> + <Insets bottom="5.0" /> + </VBox.margin> + </Label> + <Slider fx:id="faderStrope" maxHeight="150.0" minHeight="150.0" orientation="VERTICAL" prefHeight="150.0"> + <VBox.margin> + <Insets bottom="5.0" top="5.0" /> + </VBox.margin> + </Slider> + <Spinner fx:id="spinnerStrope" maxHeight="20.0" minHeight="20.0" prefHeight="20.0" prefWidth="60.0"> + <VBox.margin> + <Insets top="5.0" /> + </VBox.margin> + </Spinner> + </children> + <HBox.margin> + <Insets left="10.0" right="10.0" /> + </HBox.margin> + </VBox> + </children> + </HBox> + </children> + </VBox> + </children> + </HBox> + </children> + <padding> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </padding> + </AnchorPane> + <Label layoutX="20.0" styleClass="section-headline" text="Lichtstimmung" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="0.0"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Label> + </children> +</AnchorPane> diff --git a/src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanelController.java b/src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanelController.java new file mode 100644 index 0000000..6d8f6b7 --- /dev/null +++ b/src/lightControlSoftware/src/view/lightingMood/mainPanel/LightingMoodMainPanelController.java @@ -0,0 +1,89 @@ +package view.lightingMood.mainPanel; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import java.io.IOException; +import javafx.collections.ListChangeListener; +import javafx.event.ActionEvent; +import javafx.scene.layout.FlowPane; +import javafx.stage.Stage; +import view.lightingMood.LightingMoodListItem; +import view.lightingMood.createPanel.CreateLightingMoodController; +import viewModel.KiwiViewModelHandler; +import viewModel.group.GroupVM; +import viewModel.lightingMood.LightingMoodRepositoryVM; +import viewModel.lightingMood.LightingMoodVM; +import javafx.scene.image.Image; + +public class LightingMoodMainPanelController { + @FXML + private FlowPane flowPaneLightingMoodList; + @FXML + private Button buttonCreateNewLightingMood; + + private KiwiViewModelHandler viewModelHandler; + private LightingMoodRepositoryVM lightingMoodRepositoryVM; + private GroupVM groupVM; + + public void init(KiwiViewModelHandler viewModelHandler) { + this.viewModelHandler = viewModelHandler; + } + + // Event Listener on Button[#buttonCreateNewLightingMood].onAction + @FXML + public void createLightingMood(ActionEvent event) throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../createPanel/CreateLightingMood.fxml")); + Parent createLightingMoodPanel = loader.load(); + CreateLightingMoodController createLightingMoodController = loader.getController(); + Scene scene = new Scene(createLightingMoodPanel); + Stage lightingMoodDialog = new Stage(); + createLightingMoodController.init(lightingMoodDialog, this.viewModelHandler.getGroupRepositoryVM()); + createLightingMoodController.setGroup(this.groupVM.getGroupName()); + lightingMoodDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + lightingMoodDialog.setScene(scene); + lightingMoodDialog.setTitle("Lichtstimmung erstellen"); + lightingMoodDialog.showAndWait(); + } + + private void addLightingMoodToPane(LightingMoodVM lightingMoodVM) { + this.flowPaneLightingMoodList.getChildren().add(new LightingMoodListItem(lightingMoodVM, groupVM, this.viewModelHandler)); + } + + public void setLightingMoodRepository(GroupVM groupVM) { + this.groupVM = groupVM; + this.lightingMoodRepositoryVM = groupVM.getLightingMoodRepositoryVM(); + this.flowPaneLightingMoodList.getChildren().clear(); + this.lightingMoodRepositoryVM.lightingMoodListProperty().forEach(lightingMoodVM -> { + this.addLightingMoodToPane(lightingMoodVM); + }); + + this.lightingMoodRepositoryVM.lightingMoodListProperty().addListener((ListChangeListener.Change<? extends LightingMoodVM> change) -> { + while(change.next()) { + change.getAddedSubList().forEach(lightingMoodVM -> { + addLightingMoodToPane(lightingMoodVM); + }); + + change.getRemoved().forEach(lightingMoodVM -> { + int lightingMoodListIndex = -1; + for(int i = 0; i < this.flowPaneLightingMoodList.getChildren().size(); i++) { + if(((LightingMoodListItem)this.flowPaneLightingMoodList + .getChildren() + .get(i)) + .getLightingMoodName() + .equals(lightingMoodVM.getLightingMoodName())) { + lightingMoodListIndex = i; + break; + } + } + if(lightingMoodListIndex != -1) { + this.flowPaneLightingMoodList.getChildren().remove(lightingMoodListIndex); + } + }); + } + }); + } + +} diff --git a/src/lightControlSoftware/src/view/mainScene/MainScene.fxml b/src/lightControlSoftware/src/view/mainScene/MainScene.fxml new file mode 100644 index 0000000..20712fe --- /dev/null +++ b/src/lightControlSoftware/src/view/mainScene/MainScene.fxml @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.Menu?> +<?import javafx.scene.control.MenuBar?> +<?import javafx.scene.control.MenuItem?> +<?import javafx.scene.control.SeparatorMenuItem?> +<?import javafx.scene.control.SplitPane?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> + +<BorderPane prefHeight="700.0" prefWidth="1200.0" stylesheets="@../../../ressourcen/style/Style.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.mainScene.MainSceneController"> + <top> + <MenuBar maxHeight="25.0" minHeight="25.0" BorderPane.alignment="CENTER"> + <menus> + <Menu mnemonicParsing="false" text="Datei"> + <items> + <MenuItem fx:id="menuFileNew" mnemonicParsing="false" onAction="#createFile" text="Neu" /> + <MenuItem fx:id="menuFileLoad" mnemonicParsing="false" onAction="#loadFile" text="Laden" /> + <MenuItem fx:id="menuFileSave" mnemonicParsing="false" onAction="#saveFile" text="Speichern" /> + <MenuItem fx:id="menuFileSaveAs" mnemonicParsing="false" onAction="#saveFileAs" text="Speichern unter" /> + <SeparatorMenuItem mnemonicParsing="false" /> + <MenuItem fx:id="menuFileQuit" mnemonicParsing="false" onAction="#quit" text="Beenden" /> + </items> + </Menu> + <Menu mnemonicParsing="false" text="Lampen"> + <items> + <MenuItem fx:id="menuFixtureAdd" mnemonicParsing="false" onAction="#createFixtures" text="Hinzufügen" /> + <MenuItem fx:id="menuFixtureEditRemove" mnemonicParsing="false" onAction="#editDeleteFixtures" text="Anpassen/Löschen" /> + </items> + </Menu> + <Menu mnemonicParsing="false" text="Gruppe"> + <items> + <MenuItem mnemonicParsing="false" onAction="#createGroup" text="Hinzufügen" /> + <MenuItem mnemonicParsing="false" onAction="#editDeleteGroup" text="Anpassen/Löschen" /> + </items> + </Menu> + <Menu mnemonicParsing="false" text="Lichtstimmung"> + <items> + <MenuItem mnemonicParsing="false" onAction="#createLightingMood" text="Hinzufügen" /> + <MenuItem mnemonicParsing="false" onAction="#editDeleteLightingMood" text="Anpassen/Löschen" /> + </items> + </Menu> + <Menu mnemonicParsing="false" text="Komposition"> + <items> + <MenuItem mnemonicParsing="false" onAction="#createComposition" text="Hinzufügen" /> + <MenuItem mnemonicParsing="false" onAction="#editDeleteComposition" text="Anpassen/Löschen" /> + </items> + </Menu> + <Menu mnemonicParsing="false" text="Interface"> + <items> + <MenuItem mnemonicParsing="false" onAction="#createNode" text="Node hinzufügen" /> + <MenuItem mnemonicParsing="false" onAction="#editDeleteNode" text="Node bearbeiten/löschen" /> + </items> + </Menu> + <Menu mnemonicParsing="false" text="Hilfe"> + <items> + <MenuItem mnemonicParsing="false" onAction="#testFixtures" text="Lampen testen" /> + <SeparatorMenuItem mnemonicParsing="false" /> + <MenuItem mnemonicParsing="false" onAction="#openOnlineHelp" text="Online Hilfe" /> + <SeparatorMenuItem mnemonicParsing="false" /> + <MenuItem mnemonicParsing="false" onAction="#openAboutKIWI" text="Über KIWI" /> + </items> + </Menu> + </menus> + </MenuBar> + </top> + <center> + <BorderPane BorderPane.alignment="CENTER"> + <left> + <BorderPane fx:id="groupInterfacePanel" maxWidth="300.0" minWidth="300.0" prefWidth="300.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> + </BorderPane.margin> + </BorderPane> + </left> + <center> + <SplitPane fx:id="compositionLightingMoodPanel" orientation="VERTICAL" prefHeight="200.0" prefWidth="160.0" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets bottom="5.0" right="5.0" top="5.0" /> + </BorderPane.margin> + </SplitPane> + </center> + </BorderPane> + </center> + <bottom> + <AnchorPane maxHeight="20.0" minHeight="20.0" prefHeight="20.0" styleClass="footer" BorderPane.alignment="CENTER"> + <children> + <HBox layoutY="-178.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0"> + <children> + <Label text="Created by Sebastian Böttger"> + <padding> + <Insets left="5.0" right="5.0" /> + </padding> + </Label> + </children> + </HBox> + <HBox layoutX="10.0" layoutY="-168.0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <children> + <Label text="KIWI V. 2.0"> + <padding> + <Insets left="5.0" right="5.0" /> + </padding> + </Label> + </children> + </HBox> + </children> + </AnchorPane> + </bottom> +</BorderPane> diff --git a/src/lightControlSoftware/src/view/mainScene/MainSceneController.java b/src/lightControlSoftware/src/view/mainScene/MainSceneController.java new file mode 100644 index 0000000..cd89d5c --- /dev/null +++ b/src/lightControlSoftware/src/view/mainScene/MainSceneController.java @@ -0,0 +1,318 @@ +package view.mainScene; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.SplitPane; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.image.Image; + +import java.awt.Desktop; +import java.io.IOException; +import java.net.URI; + +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.MenuItem; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; +import view.composition.createPanel.CreateCompositionController; +import view.composition.editDeletePanel.EditDeleteCompositionController; +import view.composition.mainPanel.CompositionMainPanelController; +import view.fixture.createPanel.CreateFixtureController; +import view.fixture.editPanel.EditDeleteFixtureController; +import view.group.createPanel.CreateGroupController; +import view.group.editPanel.EditDeleteGroupController; +import view.group.mainPanel.GroupMainPanelController; +import view.lightController.mainPanel.LightControllerMainPanelController; +import view.lightingMood.createPanel.CreateLightingMoodController; +import view.lightingMood.editDeletePanel.EditDeleteLightingMoodController; +import view.lightingMood.mainPanel.LightingMoodMainPanelController; +import viewModel.KiwiViewModelHandler; + +public class MainSceneController { + @FXML + private MenuItem menuFileNew; + @FXML + private MenuItem menuFileLoad; + @FXML + private MenuItem menuFileSave; + @FXML + private MenuItem menuFileSaveAs; + @FXML + private MenuItem menuFileQuit; + @FXML + private MenuItem menuFixtureAdd; + @FXML + private MenuItem menuFixtureEditRemove; + @FXML + private BorderPane groupInterfacePanel; + @FXML + private SplitPane compositionLightingMoodPanel; + + private KiwiViewModelHandler viewModelHandler; + + public void init(KiwiViewModelHandler viewModelHandler) throws IOException { + this.viewModelHandler = viewModelHandler; + + FXMLLoader loaderCompositionPanel = new FXMLLoader(getClass().getResource("../composition/mainPanel/CompositionMainPanel.fxml")); + Parent compositionPanel = loaderCompositionPanel.load(); + CompositionMainPanelController compositionPanelController = loaderCompositionPanel.getController(); + compositionPanelController.init(viewModelHandler); + compositionLightingMoodPanel.getItems().add(compositionPanel); + + FXMLLoader loaderLightingMoodPanel = new FXMLLoader(getClass().getResource("../lightingMood/mainPanel/LightingMoodMainPanel.fxml")); + Parent lightingMoodPanel = loaderLightingMoodPanel.load(); + LightingMoodMainPanelController lightingMoodPanelController = loaderLightingMoodPanel.getController(); + lightingMoodPanelController.init(this.viewModelHandler); + compositionLightingMoodPanel.getItems().add(lightingMoodPanel); + + FXMLLoader loaderGroupPanel = new FXMLLoader(getClass().getResource("../group/mainPanel/GroupMainPanel.fxml")); + Parent groupPanel = loaderGroupPanel.load(); + GroupMainPanelController groupPanelController = loaderGroupPanel.getController(); + groupPanelController.init(viewModelHandler, lightingMoodPanelController, compositionPanelController); + groupInterfacePanel.setCenter(groupPanel); + + FXMLLoader loaderLightControllerPanel = new FXMLLoader(getClass().getResource("../lightController/mainPanel/LightControllerMainPanel.fxml")); + Parent lightControllerPanel = loaderLightControllerPanel.load(); + LightControllerMainPanelController lightControllerPanelController = loaderLightControllerPanel.getController(); + lightControllerPanelController.init(this.viewModelHandler.getArtNetControlleVM()); + groupInterfacePanel.setBottom(lightControllerPanel); + } + + // Event Listener on MenuItem[#menuFileNew].onAction + @FXML + public void createFile(ActionEvent event) { + this.viewModelHandler.clearRepros(); + } + // Event Listener on MenuItem[#menuFileLoad].onAction + @FXML + public void loadFile(ActionEvent event) { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText("Funktion nicht verf�gbar."); + alert.setContentText("Die Funktion zum Laden von Files ist aktuell noch nicht verf�gbar."); + alert.showAndWait(); + } + // Event Listener on MenuItem[#menuFileSave].onAction + @FXML + public void saveFile(ActionEvent event) { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText("Funktion nicht verf�gbar."); + alert.setContentText("Die Funktion zum Speichern von Files ist aktuell noch nicht verf�gbar."); + alert.showAndWait(); + } + // Event Listener on MenuItem[#menuFileSaveAs].onAction + @FXML + public void saveFileAs(ActionEvent event) { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText("Funktion nicht verf�gbar."); + alert.setContentText("Die Funktion zum Speichern von Files ist aktuell noch nicht verf�gbar."); + alert.showAndWait(); + } + // Event Listener on MenuItem[#menuFileQuit].onAction + @FXML + public void quit(ActionEvent event) { + Platform.exit(); + } + // Event Listener on MenuItem[#menuFixtureAdd].onAction + @FXML + public void createFixtures(ActionEvent event) throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../fixture/createPanel/CreateFixture.fxml")); + Parent createFixturePanel = loader.load(); + CreateFixtureController createFixtureController = loader.getController(); + Scene scene = new Scene(createFixturePanel); + Stage fixtureDialog = new Stage(); + createFixtureController.init(fixtureDialog, this.viewModelHandler); + fixtureDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + fixtureDialog.setScene(scene); + fixtureDialog.setTitle("Lampe hinzuf�gen"); + fixtureDialog.showAndWait(); + } + // Event Listener on MenuItem[#menuFixtureEditRemove].onAction + @FXML + public void editDeleteFixtures(ActionEvent event) throws IOException { + if(this.viewModelHandler.getFixtureRepositoryVM().fixturesRepositoryProperty().size() > 0) { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../fixture/editPanel/EditDeleteFixture.fxml")); + Parent editDeletefixturePanel = loader.load(); + EditDeleteFixtureController editDeleteFixtureController = loader.getController(); + Scene scene = new Scene(editDeletefixturePanel); + Stage fixtureDialog = new Stage(); + editDeleteFixtureController.init(fixtureDialog, this.viewModelHandler); + fixtureDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + fixtureDialog.setScene(scene); + fixtureDialog.setTitle("Lampe �ndern/l�schen"); + fixtureDialog.showAndWait(); + } else { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText("Es existieren keine Lampen."); + alert.setContentText("Bitte erstellen Sie zuerst eine Lampe."); + alert.showAndWait(); + } + } + // Event Listener on MenuItem.onAction + @FXML + public void createGroup(ActionEvent event) throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../group/createPanel/CreateGroup.fxml")); + Parent createGroupPanel = loader.load(); + CreateGroupController createGroupController = loader.getController(); + Scene scene = new Scene(createGroupPanel); + Stage groupDialog = new Stage(); + createGroupController.init(groupDialog, viewModelHandler); + groupDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + groupDialog.setScene(scene); + groupDialog.setTitle("Gruppe hinzuf�gen"); + groupDialog.showAndWait(); + + + } + // Event Listener on MenuItem.onAction + @FXML + public void editDeleteGroup(ActionEvent event) throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../group/editPanel/EditDeleteGroup.fxml")); + Parent editDeleteGroupPanel = loader.load(); + EditDeleteGroupController editDeleteGroupController = loader.getController(); + Scene scene = new Scene(editDeleteGroupPanel); + Stage groupDialog = new Stage(); + editDeleteGroupController.init(groupDialog, viewModelHandler); + groupDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + groupDialog.setScene(scene); + groupDialog.setTitle("Gruppe �ndern/l�schen"); + groupDialog.showAndWait(); + } + // Event Listener on MenuItem.onAction + @FXML + public void createLightingMood(ActionEvent event) throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../lightingMood/createPanel/CreateLightingMood.fxml")); + Parent createLightingMoodPanel = loader.load(); + CreateLightingMoodController createLightingMoodController = loader.getController(); + Scene scene = new Scene(createLightingMoodPanel); + Stage lightingMoodDialog = new Stage(); + createLightingMoodController.init(lightingMoodDialog, this.viewModelHandler.getGroupRepositoryVM()); + lightingMoodDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + lightingMoodDialog.setScene(scene); + lightingMoodDialog.setTitle("Lichtstimmung erstellen"); + lightingMoodDialog.showAndWait(); + } + // Event Listener on MenuItem.onAction + @FXML + public void editDeleteLightingMood(ActionEvent event) throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../lightingMood/editDeletePanel/EditDeleteLightingMood.fxml")); + Parent editDeleteLightingMoodPanel = loader.load(); + EditDeleteLightingMoodController editDeleteLightingMoodController = loader.getController(); + Scene scene = new Scene(editDeleteLightingMoodPanel); + Stage lightingMoodDialog = new Stage(); + editDeleteLightingMoodController.init(lightingMoodDialog, this.viewModelHandler); + lightingMoodDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + lightingMoodDialog.setScene(scene); + lightingMoodDialog.setTitle("Lichtstimmung bearbeiten/l�schen"); + lightingMoodDialog.showAndWait(); + } + // Event Listener on MenuItem.onAction + @FXML + public void createComposition(ActionEvent event) throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../composition/createPanel/CreateComposition.fxml")); + Parent createCompositionPanel = loader.load(); + CreateCompositionController createCompositionController = loader.getController(); + Scene scene = new Scene(createCompositionPanel); + Stage compositionDialog = new Stage(); + createCompositionController.init(compositionDialog, this.viewModelHandler); + compositionDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + compositionDialog.setScene(scene); + compositionDialog.setTitle("Komposition erstellen"); + compositionDialog.showAndWait(); + createCompositionController = null; + loader = null; + + } + // Event Listener on MenuItem.onAction + @FXML + public void editDeleteComposition(ActionEvent event) throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("../composition/editDeletePanel/EditDeleteComposition.fxml")); + Parent editDeleteCompositionPanel = loader.load(); + EditDeleteCompositionController editDeleteCompositionController = loader.getController(); + Scene scene = new Scene(editDeleteCompositionPanel); + Stage compositionDialog = new Stage(); + editDeleteCompositionController.init(compositionDialog, this.viewModelHandler); + compositionDialog.getIcons().add(new Image("images/Kiwi_Logo.png")); + compositionDialog.setScene(scene); + compositionDialog.setTitle("Komposition bearbeiten/l�schen"); + compositionDialog.showAndWait(); + editDeleteCompositionController = null; + loader = null; + } + // Event Listener on MenuItem.onAction + @FXML + public void createNode(ActionEvent event) { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText("Funktion nicht verf�gbar."); + alert.setContentText("Die Funktion zum Speichern von Files ist aktuell noch nicht verf�gbar."); + alert.showAndWait(); + } + // Event Listener on MenuItem.onAction + @FXML + public void editDeleteNode(ActionEvent event) { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText("Funktion nicht verf�gbar."); + alert.setContentText("Die Funktion zum Speichern von Files ist aktuell noch nicht verf�gbar."); + alert.showAndWait(); + } + // Event Listener on MenuItem.onAction + @FXML + public void testFixtures(ActionEvent event) { + Alert alert = new Alert(AlertType.WARNING); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("Warnung"); + alert.setHeaderText("Funktion nicht verf�gbar."); + alert.setContentText("Die Funktion zum Speichern von Files ist aktuell noch nicht verf�gbar."); + alert.showAndWait(); + } + // Event Listener on MenuItem.onAction + @FXML + public void openOnlineHelp(ActionEvent event) { + Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + try { + desktop.browse(new URI("https://gitlab.cvh-server.de/sboettger/kiwi-home-light-control-system")); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + // Event Listener on MenuItem.onAction + @FXML + public void openAboutKIWI(ActionEvent event) { + String headerText = "KIWI � 2024 Sebastian B�ttger\n"; + headerText += "Version: 2.0\n\n"; + headerText += "KIWI steht f�r KIWI (is a) Illumination Weeny Interface\n"; + headerText += "KIWI verwendet Art-Net 4.\n"; + headerText += "Art-Net� Designed by and Copyright Artistic Licence."; + + Alert alert = new Alert(AlertType.INFORMATION); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image("images/Kiwi_Logo.png")); + alert.setTitle("�ber KIWI"); + alert.setHeaderText(headerText); + alert.showAndWait(); + } +} diff --git a/src/lightControlSoftware/src/view/test/TestUI.java b/src/lightControlSoftware/src/view/test/TestUI.java new file mode 100644 index 0000000..067c6b8 --- /dev/null +++ b/src/lightControlSoftware/src/view/test/TestUI.java @@ -0,0 +1,89 @@ +package view.test; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +import javafx.application.Application; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; + +public class TestUI extends Application{ + + protected final PropertyChangeSupport propertySupport = new PropertyChangeSupport(this); + private PropertyChangeListener fixtureListener; + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertySupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + propertySupport.removePropertyChangeListener(listener); + } + + public void printText(String text) { + System.out.println(text); + } + + public TestUI() { + this.fixtureListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("Test")) { + if((boolean) event.getNewValue()) { + printText("True"); + } else { + printText("False"); + } + } + } + }; + } + + + public static void main(String[] args) { + launch(args); + + } + + @Override + public void start(Stage stage) throws Exception { + + ObservableList<String> options = FXCollections.observableArrayList( + "Option 1", + "Option 2", + "Option 3" + ); + ComboBox<String> comboBox = new ComboBox<>(options); + comboBox.setOnAction(event -> { + propertySupport.firePropertyChange("Test", true, false); + }); + + Button buttonAnmelden = new Button("Anmelden"); + buttonAnmelden.setOnAction(event -> { + this.addPropertyChangeListener(fixtureListener); + }); + + Button buttonAbmelden = new Button("Abmelden"); + buttonAbmelden.setOnAction(event -> { + this.removePropertyChangeListener(fixtureListener); + }); + + + Pane root = new Pane(); + HBox hBox = new HBox(comboBox, buttonAnmelden, buttonAbmelden); + root.getChildren().add(hBox); + Scene scene = new Scene(root); + stage.setMinWidth(800); + stage.setMinHeight(625); + stage.setScene(scene); + stage.show(); + + } + +} diff --git a/src/lightControlSoftware/src/viewModel/KiwiViewModelHandler.java b/src/lightControlSoftware/src/viewModel/KiwiViewModelHandler.java new file mode 100644 index 0000000..ab47c1e --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/KiwiViewModelHandler.java @@ -0,0 +1,43 @@ +package viewModel; + +import model.KiwiModelHandler; +import viewModel.fixture.FixtureGroupRepositoryVM; +import viewModel.fixture.FixtureRepositoryVM; +import viewModel.group.GroupRepositoryVM; +import viewModel.lightController.ArtNetControlleVM; + +public class KiwiViewModelHandler { + private KiwiModelHandler modelHandler; + private GroupRepositoryVM groupRepositoryVM; + private FixtureRepositoryVM fixtureRepositoryVM; + private FixtureGroupRepositoryVM fixtureGroupRepositoryVM; + private ArtNetControlleVM artNetControlleVM; + + public KiwiViewModelHandler(KiwiModelHandler modelHandler) { + this.modelHandler = modelHandler; + groupRepositoryVM = new GroupRepositoryVM(KiwiModelHandler.getGroupReposirory()); + fixtureRepositoryVM = new FixtureRepositoryVM(KiwiModelHandler.getFixtureRepository()); + fixtureGroupRepositoryVM = new FixtureGroupRepositoryVM(KiwiModelHandler.getGroupReposirory()); + artNetControlleVM = new ArtNetControlleVM(KiwiModelHandler.getArtNetController()); + } + + public GroupRepositoryVM getGroupRepositoryVM() { + return groupRepositoryVM; + } + + public FixtureRepositoryVM getFixtureRepositoryVM() { + return fixtureRepositoryVM; + } + + public FixtureGroupRepositoryVM getFixtureGroupRepositoryVM() { + return fixtureGroupRepositoryVM; + } + + public ArtNetControlleVM getArtNetControlleVM() { + return this.artNetControlleVM; + } + + public void clearRepros() { + this.modelHandler.clearRepros(); + } +} diff --git a/src/lightControlSoftware/src/viewModel/composition/CompositionRepositoryVM.java b/src/lightControlSoftware/src/viewModel/composition/CompositionRepositoryVM.java new file mode 100644 index 0000000..286c6ee --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/composition/CompositionRepositoryVM.java @@ -0,0 +1,80 @@ +package viewModel.composition; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import model.lightingScene.composition.Composition; +import model.lightingScene.composition.CompositionRepository; + +public class CompositionRepositoryVM { + + private CompositionRepository compositionRepository; + private ObservableList<CompositionVM> compositionList; + private PropertyChangeListener compositionListener; + private CompositionRepositoryVM self = this; + + public CompositionRepositoryVM(CompositionRepository compositionRepository) { + this.compositionRepository = compositionRepository; + this.compositionList = FXCollections.observableArrayList(); + this.loadCompositions(); + + this.compositionListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("compositions")) { + if(event.getNewValue() != null) { + CompositionVM compositionVM = new CompositionVM((Composition) event.getNewValue()); + compositionVM.setCompositionRepositoryVM(self); + compositionList.add(compositionVM); + } else if(event.getOldValue() != null) { + Composition composition = (Composition) event.getOldValue(); + int compositionListIndex = -1; + for(int i = 0; i < compositionList.size(); i++) { + if(compositionList.get(i).getCompositionName().equals(composition.getName())) { + compositionListIndex = i; + break; + } + } + if(compositionListIndex != -1) { + compositionList.remove(compositionListIndex); + } + } + } + } + }; + this.compositionRepository.addPropertyChangeListener(compositionListener); + } + + public void loadCompositions() { + this.compositionRepository.getCompositionMap() + .forEach((String name, Composition composition) -> { + CompositionVM compositionVM = new CompositionVM(composition); + compositionVM.setCompositionRepositoryVM(this); + this.compositionList.add(compositionVM); + }); + } + + public CompositionVM getComposition(String name) { + return this.compositionList + .stream() + .filter( + compositionVM -> compositionVM.getCompositionName().equals(name)) + .findFirst() + .orElse(null); + } + + public ObservableList<CompositionVM> compositionListProperty() { + return this.compositionList; + } + + public void disselectedOtherCompositons(String compositionName) { + this.compositionList.forEach(compositionVM -> { + if(!compositionVM.getCompositionName().equals(compositionName)) { + compositionVM.setCompositionIsSeleceted(false); + } + }); + } + +} diff --git a/src/lightControlSoftware/src/viewModel/composition/CompositionVM.java b/src/lightControlSoftware/src/viewModel/composition/CompositionVM.java new file mode 100644 index 0000000..03cd654 --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/composition/CompositionVM.java @@ -0,0 +1,112 @@ +package viewModel.composition; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; +import model.fixture.DmxValues; +import model.lightingScene.composition.Composition; +import viewModel.fixture.DmxValuesVM; + +public class CompositionVM { + + private Composition composition; + private StringProperty compositionName; + private BooleanProperty compositionIsActive; + private BooleanProperty selected = new SimpleBooleanProperty(false); + private ObservableMap<String, DmxValuesVM> compositionFixtureValues; + private PropertyChangeListener compositionListener; + private CompositionRepositoryVM compositionRepositoryVM; + + public CompositionVM(Composition composition) { + this.composition = composition; + this.compositionName = new SimpleStringProperty(composition.getName()); + this.compositionIsActive = new SimpleBooleanProperty(composition.isActive()); + this.compositionFixtureValues = FXCollections.observableHashMap(); + this.loadFixtureValues(); + this.compositionListener = new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("name")) { + compositionName.set((String)event.getNewValue()); + } else if(event.getPropertyName().equals("isActive")) { + compositionIsActive.set((boolean)event.getNewValue()); + } else if(event.getPropertyName().equals("setFixtureValues")) { + compositionFixtureValues.put((String)event.getOldValue(), new DmxValuesVM((DmxValues) event.getNewValue())); + } else if(event.getPropertyName().equals("removeFixtureValues")) { + compositionFixtureValues.remove((String)event.getOldValue()); + } + } + }; + this.composition.addPropertyChangeListener(compositionListener); + } + + public void setCompositionRepositoryVM(CompositionRepositoryVM compositionRepositoryVM) { + this.compositionRepositoryVM = compositionRepositoryVM; + } + + private void loadFixtureValues() { + this.composition.getFixtureValues().forEach((name, dmxValues) -> { + this.compositionFixtureValues.put(name, new DmxValuesVM(dmxValues)); + }); + + } + + public StringProperty compositionNameProperty() { + return this.compositionName; + } + + public BooleanProperty compositionIsActiveProperty() { + return this.compositionIsActive; + } + + public BooleanProperty selectedProperty() { + return this.selected; + } + + public void setCompositionName(String name) { + this.composition.setName(name); + } + + public void setCompositionIsSeleceted(boolean isSelected) { + if(isSelected && this.compositionRepositoryVM != null) { + this.compositionRepositoryVM.disselectedOtherCompositons(this.getCompositionName()); + } + this.selected.set(isSelected); + } + + public void activate() { + this.composition.activate(); + } + + public void deactivate() { + this.composition.deactivate(); + System.out.println(this.selected); + } + + public void setDmxValuesForFixture(String fixtureName, DmxValuesVM values) { + this.composition.setDmxValuesForFixture(fixtureName, values.getDmxValues()); + } + + public String getCompositionName() { + return this.compositionName.get(); + } + + public DmxValuesVM getDmxValuesForFixture(String fixtureName) { + return this.compositionFixtureValues.get(fixtureName); + } + + public Composition getComposition() { + return this.composition; + } + + public ObservableMap<String, DmxValuesVM> getCompositionFixtureValuesMap() { + return this.compositionFixtureValues; + } +} diff --git a/src/lightControlSoftware/src/viewModel/fixture/DmxValuesVM.java b/src/lightControlSoftware/src/viewModel/fixture/DmxValuesVM.java new file mode 100644 index 0000000..9d3b3dc --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/fixture/DmxValuesVM.java @@ -0,0 +1,89 @@ +package viewModel.fixture; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.nio.ByteBuffer; + +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import model.fixture.DmxValues; + +public class DmxValuesVM { + private DmxValues dmxValues; + private IntegerProperty mainIntensity; + private IntegerProperty strope; + private IntegerProperty red; + private IntegerProperty green; + private IntegerProperty blue; + private PropertyChangeListener dmxValueListener; + + public DmxValuesVM(DmxValues dmxValues) { + this.dmxValues = dmxValues; + this.mainIntensity = new SimpleIntegerProperty(Byte.toUnsignedInt(dmxValues.getIntensityMain())); + this.strope = new SimpleIntegerProperty(Byte.toUnsignedInt(dmxValues.getStropeRate())); + this.red = new SimpleIntegerProperty(Byte.toUnsignedInt(dmxValues.getIntensityRed())); + this.green = new SimpleIntegerProperty(Byte.toUnsignedInt(dmxValues.getIntensityGreen())); + this.blue = new SimpleIntegerProperty(Byte.toUnsignedInt(dmxValues.getIntensityBlue())); + this.dmxValueListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("intensityMain")) { + mainIntensity.setValue((int) event.getNewValue()); + } else if(event.getPropertyName().equals("stropeRate")) { + strope.setValue((int) event.getNewValue()); + } else if(event.getPropertyName().equals("intensityRed")) { + red.setValue((int) event.getNewValue()); + } else if(event.getPropertyName().equals("intensityGreen")) { + green.setValue((int) event.getNewValue()); + } else if(event.getPropertyName().equals("intensityBlue")) { + blue.setValue((int) event.getNewValue()); + } + } + }; + dmxValues.addPropertyChangeListener(dmxValueListener); + } + + public DmxValues getDmxValues() { + return this.dmxValues; + } + + public int getMainIntensity() { + return this.mainIntensity.get(); + } + + public void setMainIntensity(int mainIntensity) { + this.dmxValues.setIntensityMain(ByteBuffer.allocate(4).putInt(mainIntensity).get(3)); + } + + public int getStrope() { + return this.strope.get(); + } + + public void setStrope(int strope) { + this.dmxValues.setStropeRate(ByteBuffer.allocate(4).putInt(strope).get(3)); + } + + public int getRed() { + return this.red.get(); + } + + public void setRed(int red) { + this.dmxValues.setIntensityRed(ByteBuffer.allocate(4).putInt(red).get(3)); + } + + public int getGreen() { + return green.get(); + } + + public void setGreen(int green) { + this.dmxValues.setIntensityGreen(ByteBuffer.allocate(4).putInt(green).get(3)); + } + + public int getBlue() { + return blue.get(); + } + + public void setBlue(int blue) { + this.dmxValues.setIntensityBlue(ByteBuffer.allocate(4).putInt(blue).get(3)); + } +} diff --git a/src/lightControlSoftware/src/viewModel/fixture/FixtureGroupRepositoryVM.java b/src/lightControlSoftware/src/viewModel/fixture/FixtureGroupRepositoryVM.java new file mode 100644 index 0000000..d45a7e0 --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/fixture/FixtureGroupRepositoryVM.java @@ -0,0 +1,81 @@ +package viewModel.fixture; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import model.KiwiModelHandler; +import model.group.Group; +import model.group.GroupReposirory; + +public class FixtureGroupRepositoryVM { + private ObservableList<FixtureGroupVM> fixtureGroupsList; + private GroupReposirory groupRepository; + private PropertyChangeListener groupRepositoryListener; + + public FixtureGroupRepositoryVM(GroupReposirory groupRepository) { + this.groupRepository = groupRepository; + this.fixtureGroupsList = FXCollections.observableArrayList(); + this.loadGroups(); + this.groupRepositoryListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("groups")) { + if(event.getNewValue() != null) { + Group newGroup = (Group) event.getNewValue(); + addFixtureGroupToList(newGroup); + } else if(event.getOldValue() != null) { + Group oldGroup = (Group) event.getOldValue(); + int groupListIndex = -1; + for(int i = 0; i < fixtureGroupsList.size(); i++) { + if(fixtureGroupsList.get(i).getGroupName().equals(oldGroup.getName())) { + groupListIndex = i; + break; + } + } + if(groupListIndex != -1) { + fixtureGroupsList.remove(groupListIndex); + } + } + } + } + }; + this.groupRepository.addPropertyChangeListener(this.groupRepositoryListener); + } + + private void loadGroups() { + this.groupRepository.getGroupsMap().forEach((String name, Group group) -> { + this.addFixtureGroupToList(group); + }); + } + + private void addFixtureGroupToList(Group group) { + this.fixtureGroupsList.add(new FixtureGroupVM(group, this)); + } + + public ObservableList<FixtureGroupVM> fixtureGroupsListProperty() { + return this.fixtureGroupsList; + } + + public void newGroup(String name) { + this.groupRepository.addGroup(new Group(name, KiwiModelHandler.getLightController())); + } + + public void deleteGroup(String name) { + this.groupRepository.removeGroup(name); + } + + public void unselectOtherGroups(String groupName) { + this.fixtureGroupsList.forEach(fixtureGroupVM -> { + if(!fixtureGroupVM.getGroupName().equals(groupName) && fixtureGroupVM.getGroupSelected()) { + fixtureGroupVM.setGroupSelected(false); + } + }); + } + + @Override + protected void finalize() { + this.groupRepository.removePropertyChangeListener(this.groupRepositoryListener); + } +} diff --git a/src/lightControlSoftware/src/viewModel/fixture/FixtureGroupVM.java b/src/lightControlSoftware/src/viewModel/fixture/FixtureGroupVM.java new file mode 100644 index 0000000..a761e99 --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/fixture/FixtureGroupVM.java @@ -0,0 +1,104 @@ +package viewModel.fixture; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import model.group.Group; + +public class FixtureGroupVM { + + private IntegerProperty groupId; + private StringProperty groupName; + private BooleanProperty fixtureInGroup = new SimpleBooleanProperty(false); + private BooleanProperty groupSelected = new SimpleBooleanProperty(false); + private Group group; + private FixtureGroupRepositoryVM fixtureGroupRepositoryVM; + private FixtureRepositoryVM fixtureRepositoryVM; + private PropertyChangeListener groupListener; + + public FixtureGroupVM(Group group, FixtureGroupRepositoryVM fixtureGroupRepositoryVM) { + this.group = group; + this.fixtureRepositoryVM = new FixtureRepositoryVM(this.group.getFixtureRepository()); + this.fixtureGroupRepositoryVM = fixtureGroupRepositoryVM; + this.groupId = new SimpleIntegerProperty(group.getId()); + this.groupName = new SimpleStringProperty(group.getName()); + this.groupListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("name")) { + groupName.setValue((String)event.getNewValue()); + } + } + }; + + group.addPropertyChangeListener(this.groupListener); + } + + public String getGroupName() { + return this.groupName.get(); + } + + public void setGroupName(String groupName) { + this.group.setName(groupName); + } + + public boolean isFixtureInGroup() { + return this.fixtureInGroup.get(); + } + + public void setFixtureInGroup(boolean inGroup) { + this.fixtureInGroup.set(inGroup); + } + + public boolean getGroupSelected() { + return this.groupSelected.get(); + } + + public void setGroupSelected(boolean groupSelected) { + this.fixtureGroupRepositoryVM.unselectOtherGroups(this.getGroupName()); + this.groupSelected.set(groupSelected); + } + + public IntegerProperty groupIdProperty() { + return this.groupId; + } + + public StringProperty groupNameProperty() { + return this.groupName; + } + + public BooleanProperty fixtureInGroupProperty() { + return this.fixtureInGroup; + } + + public BooleanProperty groupSelectedProperty() { + return this.groupSelected; + } + + public FixtureGroupRepositoryVM getFixtureGroupRepositoryVM() { + return this.fixtureGroupRepositoryVM; + } + + public void addFixtureToGroup(FixtureVM fixtureVM) { + this.group.addFixture(fixtureVM.getFixture()); + } + + public void removeFixtureFromGroup(FixtureVM fixtureVM) { + this.group.removeFixture(fixtureVM.getFixture()); + } + + public FixtureRepositoryVM getFixtureRepositoryVM() { + return this.fixtureRepositoryVM; + } + + @Override + protected void finalize() { + this.group.removePropertyChangeListener(this.groupListener); + } +} diff --git a/src/lightControlSoftware/src/viewModel/fixture/FixtureRepositoryVM.java b/src/lightControlSoftware/src/viewModel/fixture/FixtureRepositoryVM.java new file mode 100644 index 0000000..3df5bb1 --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/fixture/FixtureRepositoryVM.java @@ -0,0 +1,91 @@ +package viewModel.fixture; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import model.fixture.Fixture; +import model.fixture.FixtureRepository; + +public class FixtureRepositoryVM { + private ObservableList<FixtureVM> fixtureList; + private FixtureRepository fixtureRepository; + private PropertyChangeListener fixtureRepositoryListener; + + public FixtureRepositoryVM(FixtureRepository fixtureRepository) { + this.fixtureRepository = fixtureRepository; + this.fixtureList = FXCollections.observableArrayList(); + this.loadFixtures(); + + this.fixtureRepositoryListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("fixtures")) { + if(event.getNewValue() != null) { + Fixture newFixture = (Fixture) event.getNewValue(); + addFixtureToList(newFixture); + } else if(event.getOldValue() != null) { + Fixture oldFixture = (Fixture) event.getOldValue(); + int fixtureListIndex = -1; + for(int i = 0; i < fixtureList.size(); i++) { + if(fixtureList.get(i).getFixtureName().equals(oldFixture.getName())) { + fixtureListIndex = i; + break; + } + } + if(fixtureListIndex != -1) { + fixtureList.remove(fixtureListIndex); + } + } + } + } + }; + + this.fixtureRepository.addPropertyChangeListener(this.fixtureRepositoryListener); + } + + private void loadFixtures() { + this.fixtureRepository.getFixtureMap().forEach((String name, Fixture fixture) -> { + this.addFixtureToList(fixture); + }); + } + + private void addFixtureToList(Fixture fixture) { + FixtureVM newFixture = new FixtureVM(fixture, this); + this.fixtureList.add(newFixture); + } + + public ObservableList<FixtureVM> fixturesRepositoryProperty() { + return this.fixtureList; + } + + public void newFixture(String name, int address) { + this.fixtureRepository.addFixture(new Fixture(name, address)); + } + + public void deleteFixture(String name) { + this.fixtureRepository.removeFixture(name); + } + + public FixtureVM getFixtureVM(String name) { + return this.fixtureList.stream().filter(fixtureVM -> fixtureVM.getFixtureName().equals(name)).findFirst().orElse(null); + } + + public ObservableList<FixtureVM> fixtureListProperty() { + return this.fixtureList; + } + + public void unselectOtherFixtures(String fixtureName) { + this.fixtureList.forEach(fixtureVM -> { + if(!fixtureVM.getFixtureName().equals(fixtureName) && fixtureVM.getFixtureSelected()) { + fixtureVM.setFixtureSelected(false); + } + }); + } + + @Override + protected void finalize() { + this.fixtureRepository.removePropertyChangeListener(this.fixtureRepositoryListener); + } +} diff --git a/src/lightControlSoftware/src/viewModel/fixture/FixtureVM.java b/src/lightControlSoftware/src/viewModel/fixture/FixtureVM.java new file mode 100644 index 0000000..30ae22b --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/fixture/FixtureVM.java @@ -0,0 +1,114 @@ +package viewModel.fixture; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import model.fixture.Fixture; + +public class FixtureVM { + + private StringProperty fixtureName; + private IntegerProperty fixtureAddress; + private Fixture fixture; + private BooleanProperty fixtureInGroup = new SimpleBooleanProperty(false); + private BooleanProperty fixtureSelected = new SimpleBooleanProperty(false); + private PropertyChangeListener fixtureListener; + private FixtureRepositoryVM fixtureRepositoryVM; + private DmxValuesVM dmxValuesVM; + private String lightingMoodName = "Benutzerdefeniert"; + + public FixtureVM(Fixture fixture, FixtureRepositoryVM fixtureRepositoryVM) { + this.fixture = fixture; + this.fixtureRepositoryVM = fixtureRepositoryVM; + this.dmxValuesVM = new DmxValuesVM(fixture.getDmxValues()); + this.fixtureName = new SimpleStringProperty(this.fixture.getName()); + this.fixtureAddress = new SimpleIntegerProperty(this.fixture.getAddress()); + this.fixtureListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("name")) { + fixtureName.setValue((String)event.getNewValue()); + } else if(event.getPropertyName().equals("address")) { + fixtureAddress.setValue((Integer)event.getNewValue()); + } + } + }; + this.fixture.addPropertyChangeListener(this.fixtureListener); + } + + public String getFixtureName() { + return fixtureName.get(); + } + + public void setFixtureName(String fixtureName) { + this.fixture.setName(fixtureName); + } + + public int getFixtureAddress() { + return fixtureAddress.get(); + } + + public void setFixtureAddress(int fixtureAddress) { + this.fixture.setAddress(fixtureAddress); + } + + public boolean getFixtureSelected() { + return this.fixtureSelected.get(); + } + + public void setFixtureSelected(boolean selected) { + this.fixtureRepositoryVM.unselectOtherFixtures(this.fixtureName.get()); + this.fixtureSelected.set(selected); + } + + public void setFixtureInGroup(boolean inGroup) { + this.fixtureInGroup.set(inGroup); + } + + public boolean isFixtureInGroup() { + return this.fixtureInGroup.get(); + } + + public StringProperty fixtureNameProperty() { + return this.fixtureName; + } + + public IntegerProperty fixtureAddressProperty() { + return this.fixtureAddress; + } + + public Fixture getFixture() { + return this.fixture; + } + + public BooleanProperty fixtureInGroupProperty() { + return this.fixtureInGroup; + } + + public BooleanProperty fixtureSelectedProperty() { + return this.fixtureSelected; + } + + public DmxValuesVM getDmxValuesVM() { + return this.dmxValuesVM; + } + + public String getLightingMoodName() { + return lightingMoodName; + } + + public void setLightingMoodName(String lightingMoodName) { + this.lightingMoodName = lightingMoodName; + } + + @Override + protected void finalize() { + this.fixture.removePropertyChangeListener(this.fixtureListener); + } +} diff --git a/src/lightControlSoftware/src/viewModel/group/GroupRepositoryVM.java b/src/lightControlSoftware/src/viewModel/group/GroupRepositoryVM.java new file mode 100644 index 0000000..67aa771 --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/group/GroupRepositoryVM.java @@ -0,0 +1,103 @@ +package viewModel.group; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import model.KiwiModelHandler; +import model.group.Group; +import model.group.GroupReposirory; + +public class GroupRepositoryVM { + private ObservableList<GroupVM> groupsList; + private GroupReposirory groupRepository; + private PropertyChangeListener groupRepositoryListener; + private ObjectProperty<GroupVM> currentSelectedGroup = new SimpleObjectProperty<>(); + + + public GroupRepositoryVM(GroupReposirory groupRepository) { + this.groupRepository = groupRepository; + this.groupsList = FXCollections.observableArrayList(); + this.loadGroups(); + this.groupRepositoryListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("groups")) { + if(event.getNewValue() != null) { + Group newGroup = (Group) event.getNewValue(); + addGroupToList(newGroup); + } else if(event.getOldValue() != null) { + Group oldGroup = (Group) event.getOldValue(); + int groupListIndex = -1; + for(int i = 0; i < groupsList.size(); i++) { + if(groupsList.get(i).getGroupName().equals(oldGroup.getName())) { + groupListIndex = i; + break; + } + } + if(groupListIndex != -1) { + groupsList.remove(groupListIndex); + } + } + } + } + }; + this.groupRepository.addPropertyChangeListener(this.groupRepositoryListener); + } + + private void loadGroups() { + this.groupRepository.getGroupsMap().forEach((String name, Group group) -> { + this.addGroupToList(group); + }); + } + + private void addGroupToList(Group group) { + this.groupsList.add(new GroupVM(group, this)); + } + + public ObservableList<GroupVM> groupsListProperty() { + return this.groupsList; + } + + public void newGroup(String name) { + this.groupRepository.addGroup(new Group(name, KiwiModelHandler.getLightController())); + } + + public void deleteGroup(String name) { + this.groupRepository.removeGroup(name); + } + + public GroupVM getGroupVM(String name) { + return this.groupsList.stream() + .filter(groupVM -> groupVM + .getGroupName().equals(name)) + .findFirst() + .orElse(null); + } + + public void unselectOtherGroups(String groupName) { + this.currentSelectedGroup.set(this.groupsList + .stream() + .filter(groupVM -> groupVM.getGroupName() == groupName) + .findFirst() + .orElse(null)); + + this.groupsList.forEach(groupVM -> { + if(!groupVM.getGroupName().equals(groupName) && groupVM.getGroupSelected()) { + groupVM.setGroupSelected(false); + } + }); + } + + public ObjectProperty<GroupVM> currentSelectedGroupProperty() { + return this.currentSelectedGroup; + } + + @Override + protected void finalize() { + this.groupRepository.removePropertyChangeListener(this.groupRepositoryListener); + } +} diff --git a/src/lightControlSoftware/src/viewModel/group/GroupVM.java b/src/lightControlSoftware/src/viewModel/group/GroupVM.java new file mode 100644 index 0000000..52b721d --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/group/GroupVM.java @@ -0,0 +1,200 @@ +package viewModel.group; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import model.group.Group; +import viewModel.composition.CompositionRepositoryVM; +import viewModel.composition.CompositionVM; +import viewModel.fixture.FixtureRepositoryVM; +import viewModel.fixture.FixtureVM; +import viewModel.lightingMood.LightingMoodRepositoryVM; +import viewModel.lightingMood.LightingMoodVM; + +public class GroupVM { + + public static enum State{ + OFF, + MID, + ON + }; + + private IntegerProperty groupId; + private StringProperty groupName; + private ObjectProperty<State> groupState = new SimpleObjectProperty<>(State.OFF); + private BooleanProperty groupSelected = new SimpleBooleanProperty(false); + private Group group; + private GroupRepositoryVM groupRepositoryVM; + private PropertyChangeListener groupListener; + private LightingMoodRepositoryVM lightingMoodRepositoryVM; + private CompositionRepositoryVM compositionRepositoryVM; + private FixtureRepositoryVM fixtureRepositoryVM; + + public GroupVM(Group group, GroupRepositoryVM groupRepositoryVM) { + this.group = group; + this.groupRepositoryVM = groupRepositoryVM; + this.fixtureRepositoryVM = new FixtureRepositoryVM(this.group.getFixtureRepository()); + this.lightingMoodRepositoryVM = new LightingMoodRepositoryVM(this.group.getLightingMoodRepository()); + this.compositionRepositoryVM = new CompositionRepositoryVM(this.group.getCompositionRepository()); + this.groupId = new SimpleIntegerProperty(group.getId()); + this.groupName = new SimpleStringProperty(group.getName()); + this.groupListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("name")) { + groupName.setValue((String)event.getNewValue()); + } else if(event.getPropertyName().equals("currentState")) { + switch((model.group.Group.State) event.getNewValue()) { + case AKTIV: + groupState.setValue(State.ON); + break; + case PARTAKTIV: + groupState.setValue(State.MID); + break; + case DEAKTIV: + groupState.setValue(State.OFF); + break; + } + } + } + }; + + group.addPropertyChangeListener(this.groupListener); + this.groupState.addListener((object, oldValue, newValue) -> { + this.setGroupState((State) newValue); + }); + } + + public String getGroupName() { + return this.groupName.get(); + } + + public void setGroupName(String groupName) { + this.group.setName(groupName); + } + + public State getGroupState() { + return this.groupState.get(); + } + + public void setGroupState(State groupState) { + switch(groupState) { + case ON: + this.group.activate(); + break; + case MID: + this.group.setCurrentState(model.group.Group.State.PARTAKTIV); + break; + case OFF: + this.group.deactivate(); + break; + } + } + + public boolean getGroupSelected() { + return this.groupSelected.get(); + } + + public void setGroupSelected(boolean groupSelected) { + if(groupSelected) { + this.groupRepositoryVM.unselectOtherGroups(this.getGroupName()); + } + this.groupSelected.set(groupSelected); + } + + public FixtureRepositoryVM getFixtureRepositoryVM() { + return this.fixtureRepositoryVM; + } + + public LightingMoodRepositoryVM getLightingMoodRepositoryVM() { + return this.lightingMoodRepositoryVM; + } + + public CompositionRepositoryVM getCompositionRepositoryVM() { + return this.compositionRepositoryVM; + } + + public IntegerProperty groupIdProperty() { + return this.groupId; + } + + public StringProperty groupNameProperty() { + return this.groupName; + } + + public ObjectProperty<State> groupStateProperty() { + return this.groupState; + } + + public BooleanProperty groupSelectedProperty() { + return this.groupSelected; + } + + public Group getGroup() { + return this.group; + } + + public void addFixture(FixtureVM fixtureVM) { + this.group.addFixture(fixtureVM.getFixture()); + } + + public void removeFixture(FixtureVM fixtureVM) { + this.group.removeFixture(fixtureVM.getFixture()); + } + + public boolean isFixtureInGroup(String name) { + if(this.group.getFixtureRepository().getFixture(name) == null) { + return false; + } else { + return true; + } + } + + public void addLightingMood(LightingMoodVM lightingMoodVM) { + this.group.addLightingMood(lightingMoodVM.getLightingMood()); + } + + public void activateLightingMood(LightingMoodVM lightingMoodVM) { + this.group.activateLightingMood( + this.lightingMoodRepositoryVM + .getLightingMood(lightingMoodVM.getLightingMoodName()).getLightingMood()); + } + + public void deactivateLightingMood(LightingMoodVM lightingMoodVM) { + this.group.deactivateLightingMood( + this.lightingMoodRepositoryVM + .getLightingMood(lightingMoodVM.getLightingMoodName()).getLightingMood()); + } + + public void removeLightingMood(LightingMoodVM ligtingMoodVM) { + this.group.removeLightingMood(ligtingMoodVM.getLightingMood()); + } + + public void addComposition(CompositionVM compositionVM) { + this.group.addComposition(compositionVM.getComposition()); + } + + public void activateComposition(CompositionVM compositionVM) { + this.group.activateComposition(compositionVM.getComposition()); + } + + public void deactivateComposition(CompositionVM compositionVM) { + this.group.deactivateComposition(compositionVM.getComposition()); + } + + public void removeComposition(CompositionVM composition) { + this.group.removeComposition(composition.getComposition()); + } + + @Override + protected void finalize() { + this.group.removePropertyChangeListener(this.groupListener); + } +} diff --git a/src/lightControlSoftware/src/viewModel/lightController/ArtNetControlleVM.java b/src/lightControlSoftware/src/viewModel/lightController/ArtNetControlleVM.java new file mode 100644 index 0000000..b2e4350 --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/lightController/ArtNetControlleVM.java @@ -0,0 +1,26 @@ +package viewModel.lightController; + +import model.lightController.artNet.ArtNetController; + +public class ArtNetControlleVM { + private ArtNetNodeRepositoryVM artNetNodeRepositoryVM; + private ArtNetController artNetController; + + public ArtNetControlleVM(ArtNetController artNetController) { + this.artNetController = artNetController; + this.artNetNodeRepositoryVM = new ArtNetNodeRepositoryVM(this.artNetController.getNodeRepository()); + } + + public void connectToNodes() { + this.artNetController.connectToNodes(); + } + + public void disconnectFromNodes() { + this.artNetController.disconnectNodes(); + } + + public ArtNetNodeRepositoryVM getArtNetNodeRepositoryVM() { + return this.artNetNodeRepositoryVM; + } +} + diff --git a/src/lightControlSoftware/src/viewModel/lightController/ArtNetNodeRepositoryVM.java b/src/lightControlSoftware/src/viewModel/lightController/ArtNetNodeRepositoryVM.java new file mode 100644 index 0000000..ddacad1 --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/lightController/ArtNetNodeRepositoryVM.java @@ -0,0 +1,54 @@ +package viewModel.lightController; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import model.lightController.artNet.artNetNode.ArtNetNodeRepository; +import viewModel.group.GroupVM.State; + +public class ArtNetNodeRepositoryVM { + private ArtNetNodeRepository artNetNodeRepository; + private BooleanProperty nodesConnected = new SimpleBooleanProperty(false); + private ObjectProperty<State> connectState = new SimpleObjectProperty<>(State.OFF); + private PropertyChangeListener nodeRepositoryListener; + + public ArtNetNodeRepositoryVM(ArtNetNodeRepository artNetNodeRepository) { + this.artNetNodeRepository = artNetNodeRepository; + this.nodeRepositoryListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("nodesConnected")) { + nodesConnected.set((boolean) event.getNewValue()); + if((boolean) event.getNewValue()) { + connectState.set(State.ON); + } else { + if(connectState.get() == State.OFF) { + connectState.set(State.MID); + } else { + connectState.set(State.OFF); + } + + } + } + } + }; + + this.artNetNodeRepository.addPropertyChangeListener(this.nodeRepositoryListener); + } + + public boolean isNodesConneted() { + return this.nodesConnected.get(); + } + + public BooleanProperty nodesConnectedProperty() { + return this.nodesConnected; + } + + public ObjectProperty<State> connectStateProperty() { + return this.connectState; + } +} diff --git a/src/lightControlSoftware/src/viewModel/lightingMood/LightingMoodRepositoryVM.java b/src/lightControlSoftware/src/viewModel/lightingMood/LightingMoodRepositoryVM.java new file mode 100644 index 0000000..e334787 --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/lightingMood/LightingMoodRepositoryVM.java @@ -0,0 +1,94 @@ +package viewModel.lightingMood; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import model.lightingScene.lightingMood.LightingMood; +import model.lightingScene.lightingMood.LightingMoodRepository; + +public class LightingMoodRepositoryVM { + private ObservableList<LightingMoodVM> lightingMoodList; + private LightingMoodRepository lightingMoodRepository; + private PropertyChangeListener lightingMoodListener; + private LightingMoodRepositoryVM self = this; + + + public LightingMoodRepositoryVM(LightingMoodRepository lightingMoodRepository) { + this.lightingMoodRepository = lightingMoodRepository; + this.lightingMoodList = FXCollections.observableArrayList(); + this.loadLightingMoods(); + this.lightingMoodListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("lightingMoods")) { + if(event.getNewValue() != null) { + LightingMoodVM lightingMoodVM = new LightingMoodVM((LightingMood) event.getNewValue()); + lightingMoodVM.setLightingMoodRepositoryVM(self); + lightingMoodList.add(lightingMoodVM); + } else if(event.getOldValue() != null) { + LightingMood lightingMood = (LightingMood) event.getOldValue(); + int lightingMoodListIndex = -1; + for(int i = 0; i < lightingMoodList.size(); i++) { + if(lightingMoodList.get(i).getLightingMoodName().equals(lightingMood.getName())) { + lightingMoodListIndex = i; + break; + } + } + if(lightingMoodListIndex != -1) { + lightingMoodList.remove(lightingMoodListIndex); + } + } + } + } + }; + + this.lightingMoodRepository.addPropertyChangeListener(this.lightingMoodListener); + } + + private void loadLightingMoods() { + this.lightingMoodRepository.getLightingMoodMap() + .forEach((String name, LightingMood lightingMood) -> { + LightingMoodVM lightingMoodVM = new LightingMoodVM(lightingMood); + lightingMoodVM.setLightingMoodRepositoryVM(this); + this.lightingMoodList.add(lightingMoodVM); + + }); + } + + public void addLightingMood(LightingMoodVM lightingMoodVM) { + lightingMoodVM.setLightingMoodRepositoryVM(this); + this.lightingMoodRepository.addLightingMood(lightingMoodVM.getLightingMood()); + } + + public void removeLightingMood(LightingMoodVM lightingMoodVM) { + this.lightingMoodRepository.removeLightingMood(lightingMoodVM.getLightingMoodName()); + } + + public LightingMoodVM getLightingMood(String name) { + return this.lightingMoodList + .stream() + .filter( + lightingMood -> lightingMood.getLightingMoodName() + .equals(name)) + .findFirst() + .orElse(null); + } + + public ObservableList<LightingMoodVM> lightingMoodListProperty() { + return this.lightingMoodList; + } + + public void disableOtherLightingMoods(String name) { + this.lightingMoodList.forEach(lightingMoodVM -> { + if(!lightingMoodVM.getLightingMoodName().equals(name)) { + lightingMoodVM.LightingMoodIsSelectedProperty().set(false); + } + }); + } + + @Override + protected void finalize() { + this.lightingMoodRepository.removePropertyChangeListener(this.lightingMoodListener); + } +} diff --git a/src/lightControlSoftware/src/viewModel/lightingMood/LightingMoodVM.java b/src/lightControlSoftware/src/viewModel/lightingMood/LightingMoodVM.java new file mode 100644 index 0000000..489618d --- /dev/null +++ b/src/lightControlSoftware/src/viewModel/lightingMood/LightingMoodVM.java @@ -0,0 +1,225 @@ +package viewModel.lightingMood; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import model.lightingScene.lightingMood.LightingMood; +import viewModel.fixture.DmxValuesVM; + +public class LightingMoodVM { + private LightingMood lightingMood; + private StringProperty lightingMoodName; + private BooleanProperty lightingMoodIsActive; + private BooleanProperty lightingMoodIsSelected = new SimpleBooleanProperty(false); + private IntegerProperty lightingMoodMainIntensity; + private IntegerProperty lightingMoodStrope; + private IntegerProperty lightingMoodRed; + private IntegerProperty lightingMoodGreen; + private IntegerProperty lightingMoodBlue; + private PropertyChangeListener lightingMoodListener; + private PropertyChangeListener dmxValueListener; + private LightingMoodRepositoryVM lightingMoodRepositoryVM; + + + public LightingMoodVM(LightingMood lightingMood) { + this.lightingMood = lightingMood; + this.lightingMoodName = new SimpleStringProperty(lightingMood.getName()); + this.lightingMoodIsActive = new SimpleBooleanProperty(lightingMood.isActive()); + this.lightingMoodMainIntensity = new SimpleIntegerProperty(Byte.toUnsignedInt(lightingMood.getDmxValues().getIntensityMain())); + this.lightingMoodStrope = new SimpleIntegerProperty(Byte.toUnsignedInt(lightingMood.getDmxValues().getStropeRate())); + this.lightingMoodRed = new SimpleIntegerProperty(Byte.toUnsignedInt(lightingMood.getDmxValues().getIntensityRed())); + this.lightingMoodGreen = new SimpleIntegerProperty(Byte.toUnsignedInt(lightingMood.getDmxValues().getIntensityGreen())); + this.lightingMoodBlue = new SimpleIntegerProperty(Byte.toUnsignedInt(lightingMood.getDmxValues().getIntensityBlue())); + + this.setupListener(); + this.lightingMood.addPropertyChangeListener(lightingMoodListener); + this.lightingMood.getDmxValues().addPropertyChangeListener(dmxValueListener); + } + + public void setLightingMoodRepositoryVM(LightingMoodRepositoryVM lightingMoodRepositoryVM) { + this.lightingMoodRepositoryVM = lightingMoodRepositoryVM; + } + + public LightingMood getLightingMood() { + return lightingMood; + } + + public String getLightingMoodName() { + return lightingMoodName.get(); + } + + public void setLightingMoodName(String name) { + this.lightingMood.setName(name); + } + + public boolean getLightingMoodIsActive() { + return lightingMoodIsActive.get(); + } + + public boolean getLightingMoodIsSelected() { + return lightingMoodIsSelected.get(); + } + + public void setLightingMoodIsSelected(boolean isSelected) { + if(isSelected && this.lightingMoodRepositoryVM != null) { + this.lightingMoodRepositoryVM.disableOtherLightingMoods(this.getLightingMoodName()); + } + this.lightingMoodIsSelected.set(isSelected); + } + + public int getLightingMoodMainIntensity() { + return lightingMoodMainIntensity.get(); + } + + public void setLightingMoodMainIntensity(int mainIntensity) { + this.lightingMood.getDmxValues().setIntensityMain((byte) mainIntensity); + } + + public int getLightingMoodStrope() { + return lightingMoodStrope.get(); + } + + public void setLightingMoodStrope(int strope) { + this.lightingMood.getDmxValues().setStropeRate((byte) strope); + } + + public int getLightingMoodRed() { + return lightingMoodRed.get(); + } + + public void setLightingMoodRed(int red) { + this.lightingMood.getDmxValues().setIntensityRed((byte) red); + } + + public int getLightingMoodGreen() { + return lightingMoodGreen.get(); + } + + public void setLightingMoodGreen(int green) { + this.lightingMood.getDmxValues().setIntensityGreen((byte) green); + } + + public int getLightingMoodBlue() { + return lightingMoodBlue.get(); + } + + public void setLightingMoodBlue(int blue) { + this.lightingMood.getDmxValues().setIntensityBlue((byte) blue); + } + + public StringProperty LightingMoodNameProperty() { + return lightingMoodName; + } + + public BooleanProperty LightingMoodIsActiveProperty() { + return lightingMoodIsActive; + } + + public BooleanProperty LightingMoodIsSelectedProperty() { + return lightingMoodIsSelected; + } + + public IntegerProperty LightingMoodMainIntensityProperty() { + return lightingMoodMainIntensity; + } + + public IntegerProperty LightingMoodStropeProperty() { + return lightingMoodStrope; + } + + public IntegerProperty LightingMoodRedProperty() { + return lightingMoodRed; + } + + public IntegerProperty LightingMoodGreenProperty() { + return lightingMoodGreen; + } + + public IntegerProperty LightingMoodBlueProperty() { + return lightingMoodBlue; + } + + public PropertyChangeListener LightingMoodListenerProperty() { + return lightingMoodListener; + } + + public void activate() { + this.lightingMood.activate(); + } + + public void deactivate() { + this.lightingMood.deactivate(); + } + + public void setupListener() { + this.lightingMoodListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("name")) { + lightingMoodName.set((String) event.getNewValue()); + } else if(event.getPropertyName().equals("isActive")) { + lightingMoodIsActive.set((boolean) event.getNewValue()); + } + } + }; + + this.dmxValueListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + if(event.getPropertyName().equals("intensityMain")) { + lightingMoodMainIntensity.set((int)event.getNewValue()); + } else if(event.getPropertyName().equals("stropeRate")) { + lightingMoodStrope.set((int)event.getNewValue()); + } else if(event.getPropertyName().equals("intensityRed")) { + lightingMoodRed.set((int)event.getNewValue()); + } else if(event.getPropertyName().equals("intensityGreen")) { + lightingMoodGreen.set((int)event.getNewValue()); + } else if(event.getPropertyName().equals("intensityBlue")) { + lightingMoodBlue.set((int)event.getNewValue()); + } + } + }; + + this.lightingMoodName.addListener((object, oldValue, newValue) -> { + this.lightingMood.setName(newValue); + }); + + this.lightingMoodIsActive.addListener((object, oldValue, newValue) -> { + if(newValue) { + this.lightingMood.activate(); + } else { + this.lightingMood.deactivate(); + } + }); + + this.lightingMoodMainIntensity.addListener((object, oldValue, newValue) -> { + this.lightingMood.getDmxValues().setIntensityMain(newValue.byteValue()); + }); + + this.lightingMoodStrope.addListener((object, oldValue, newValue) -> { + this.lightingMood.getDmxValues().setStropeRate(newValue.byteValue()); + }); + + this.lightingMoodRed.addListener((object, oldValue, newValue) -> { + this.lightingMood.getDmxValues().setIntensityRed(newValue.byteValue()); + }); + + this.lightingMoodGreen.addListener((object, oldValue, newValue) -> { + this.lightingMood.getDmxValues().setIntensityGreen(newValue.byteValue()); + }); + + this.lightingMoodBlue.addListener((object, oldValue, newValue) -> { + this.lightingMood.getDmxValues().setIntensityBlue(newValue.byteValue()); + }); + } + + public DmxValuesVM getDmxValuesVM() { + return new DmxValuesVM(this.lightingMood.getDmxValues()); + } + +} diff --git a/src/lightControlUnit/ArtNetController/ArtCommands.h b/src/lightControlUnit/ArtNetController/ArtCommands.h new file mode 100644 index 0000000..0f46e12 --- /dev/null +++ b/src/lightControlUnit/ArtNetController/ArtCommands.h @@ -0,0 +1,48 @@ +struct activatePresetStructure +{ + char *presetId = "presetId"; +}; + +struct deactivatePresetStructure +{ + char *presetId = "presetId"; +}; + +struct clearPresetStructure +{ + char *presetId = "presetId"; +}; + +struct setPresetStructure +{ + char *presetId = "presetId"; + char *groupId = "groupId"; + char *channel = "channel"; + char *value = "value"; +}; + +struct setChannelStateStructure +{ + char *channel = "channel"; + char *isActive = "isActive"; +}; + +struct activateGroupStructure +{ + char *groupId = "groupId"; +}; + +struct deactivateGroupStructure +{ + char *groupId = "groupId"; +}; + +struct presetActivatedStructure +{ + char *presetId = "presetId"; +}; + +struct presetDeactivatedStructure +{ + char *presetId = "presetId"; +}; \ No newline at end of file diff --git a/src/lightControlUnit/ArtNetController/ArtNetController.cpp b/src/lightControlUnit/ArtNetController/ArtNetController.cpp new file mode 100644 index 0000000..a793257 --- /dev/null +++ b/src/lightControlUnit/ArtNetController/ArtNetController.cpp @@ -0,0 +1,676 @@ +#include "ArtNetController.h" + +//******************************************************** +//**Art-Net™ Designed by and Copyright Artistic Licence.** +//******************************************************** + +ArtNetController::ArtNetController(){} + +ArtNetController::ArtNetController (DmxController* dmxController, PresetRepository* presetRepository) +{ + this->startUdp(); + this->dmxController = dmxController; + this->presetRepository = presetRepository; + + for(int i = 0; i < 4;i++) + { + this->controllerIpAddress[i] = 0x00; + } +} + +int ArtNetController::startUdp() +{ + this->mac[0] = MAC_ADDRESS_PART_01; + this->mac[1] = MAC_ADDRESS_PART_02; + this->mac[2] = MAC_ADDRESS_PART_03; + this->mac[3] = MAC_ADDRESS_PART_04; + this->mac[4] = MAC_ADDRESS_PART_05; + this->mac[5] = MAC_ADDRESS_PART_06; + + if (Ethernet.begin(this->mac) == 0) + { + Serial.println("Failed to configure Ethernet using DHCP"); + if (Ethernet.hardwareStatus() == EthernetNoHardware) + { + Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); + } + else if (Ethernet.linkStatus() == LinkOFF) + { + Serial.println("Ethernet cable is not connected."); + } + } + + this->nodeIpAddress[0] = Ethernet.localIP()[0]; + this->nodeIpAddress[1] = Ethernet.localIP()[1]; + this->nodeIpAddress[2] = Ethernet.localIP()[2]; + this->nodeIpAddress[3] = Ethernet.localIP()[3]; + + this->udp.begin(UDP_PORT_ADRESS); +} + +bool ArtNetController::checkArtNetHeader(uint8_t udpData[]) +{ + bool headerFound = true; + uint8_t artNetHeader[8] = {'A','r','t','-','N','e','t',0x00}; + for(int i = 0; i < 8; i++) + { + if(udpData[i] != artNetHeader[i]) + { + headerFound = false; + } + } + + return headerFound; +} + +uint8_t *ArtNetController::getPresetStatus() +{ + uint8_t *presetsStatus = new uint8_t[NUMBER_OF_PRESETS]; + for(int i = 0; i < NUMBER_OF_PRESETS; i++) + { + presetsStatus[i] = this->presetRepository->getPresetState(i); + } + + return presetsStatus; +} + +void ArtNetController::sendPresetActivated(int presetId) +{ + String commandString = String(ART_COM_PA) + "={\"presetId\":" + String(presetId) + "}"; + uint16_t length = commandString.length(); + char command[length]; + commandString.toCharArray(command, length+1); + this->sendArtCommand(command, length); +} + +void ArtNetController::sendPresetDeactivated(int presetId) +{ + String commandString = String(ART_COM_PD) + "={\"presetId\":" + String(presetId) + "}"; + uint16_t length = commandString.length(); + char command[length]; + commandString.toCharArray(command, length+1); + this->sendArtCommand(command, length); +} + +void ArtNetController::listenToArtNet() +{ + + int packetSize = this->udp.parsePacket(); + if(packetSize >= 14) + { + // read the packet into packetBufferRead + uint8_t *packetBufferRead = new uint8_t[packetSize]; + this->udp.read(packetBufferRead,packetSize); + + if(this->checkArtNetHeader(packetBufferRead)) + { + uint16_t opCode = (((uint16_t)packetBufferRead[9] << 8) + packetBufferRead[8]); + switch(opCode) + { + case OP_POLL: + // save ip address from controller + for(int i = 0; i < 4;i++) + { + this->controllerIpAddress[i] = this->udp.remoteIP()[i]; + } + this->sendArtPollReply(); + break; + + case OP_COMMAND: + this->processArtCommand(packetBufferRead, packetSize); + break; + + case OP_DATA_REQUEST: + // save ip address from controller + for(int i = 0; i < 4;i++) + { + this->controllerIpAddress[i] = this->udp.remoteIP()[i]; + } + this->processArtDataRequest(packetBufferRead, packetSize); + break; + + case OP_DMX: + this->processArtDmx(packetBufferRead, packetSize); + } + } + } +} + +ArtCommand ArtNetController::parseToArtCommand(uint8_t updData[], int length) +{ + ArtCommand artCommand; + + if(length >= 16) + { + // protocoll version + artCommand.ProtVerHi = updData[10]; + artCommand.ProtVerLo = updData[11]; + + // esta manifacture + artCommand.EstaManHi = updData[12]; + artCommand.EstaManLo = updData[13]; + + // lengths + artCommand.Length = (((uint16_t)updData[14] << 8) + updData[15]); + + if(artCommand.Length > 0) + { + artCommand.Data = new char[artCommand.Length + 1]; + for(int i = 0; i < artCommand.Length; i++) + { + artCommand.Data[i] = (char)updData[16 + i]; + } + artCommand.Data[artCommand.Length] = '\0'; + } + } + + return artCommand; +} + +ArtDataRequest ArtNetController::parseToArtDataRequest(uint8_t updData[], int length) +{ + ArtDataRequest artDataRequest; + if(length >= 18) + { + // protocoll version + artDataRequest.ProtVerHi = updData[10]; + artDataRequest.ProtVerLo = updData[11]; + + // esta manifacture + artDataRequest.EstaManHi = updData[12]; + artDataRequest.EstaManLo = updData[13]; + + // oem + artDataRequest.OemHi = updData[14]; + artDataRequest.OemLo = updData[15]; + + // request code + artDataRequest.RequestCode = (((uint16_t)updData[16] << 8) + updData[17]); + } + + return artDataRequest; +} + +ArtDmx ArtNetController::parseToArtDmx(uint8_t updData[], int length) +{ + ArtDmx artDmx; + if(length >= 18) + { + // protocoll version + artDmx.ProtVerHi = updData[10]; + artDmx.ProtVerLo = updData[11]; + + // sequence + artDmx.Sequence = updData[12]; + + // pysical input port + artDmx.Physical = updData[13]; + + // art-net address + artDmx.SubUni = updData[14]; + artDmx.Net = updData[15]; + + artDmx.Length = (((uint16_t)updData[16] << 8) + updData[17]); + + if(artDmx.Length > 0) + { + artDmx.Data = new uint8_t[artDmx.Length]; + for(int i = 0; i < artDmx.Length; i++) + { + artDmx.Data[i] = updData[18+i]; + } + } + } + + return artDmx; +} + +void ArtNetController::parseFromArtPollReply(ArtPollReply artPollReply, uint8_t* udpSendData, int length) +{ + if(length >= 207) + { + // id + for(int i = 0; i<8; i++) + { + udpSendData[i] = artPollReply.Id[i]; + } + + // op code + udpSendData[8] = artPollReply.OpCode; + udpSendData[9] = artPollReply.OpCode >> 8; + + // node ip + for(int i= 0; i < 4; i++) + { + udpSendData[10+i] = artPollReply.IpAddress[i]; + } + + // port + udpSendData[14] = artPollReply.Port; + udpSendData[15] = artPollReply.Port>> 8; + + // version info + udpSendData[16] = artPollReply.VersInfoH; + udpSendData[17] = artPollReply.VersInfoL; + + // net address + udpSendData[18] = artPollReply.NetSwitch; + + // sub net address + udpSendData[19] = artPollReply.SubSwitch; + + // oem + udpSendData[20] = artPollReply.OemHi; + udpSendData[21] = artPollReply.OemLo; + + // ubea version + udpSendData[22] = artPollReply.UbeaVersion; + + // status + udpSendData[23] = artPollReply.Status1; + + // esta + udpSendData[24] = artPollReply.EstaManLo; + udpSendData[25] = artPollReply.EstaManHi; + + // port name + for (int i = 0; i < 18; i++) + { + udpSendData[26+i] = artPollReply.PortName[i]; + } + + // long name + for (int i = 0; i < 64; i++) + { + udpSendData[44+i] = artPollReply.LongName[i]; + } + + // node report + for (int i = 0; i < 64; i++) + { + udpSendData[108+i] = artPollReply.NodeReport[i]; + } + + // number of ports + udpSendData[172] = artPollReply.NumPortsHi; + udpSendData[173] = artPollReply.NumPortsLo; + + // port type + for (int i = 0; i < 4; i++) + { + udpSendData[174+i] = artPollReply.PortTypes[i]; + } + + // good inputs + for (int i = 0; i < 4; i++) + { + udpSendData[178+i] = artPollReply.GoodInput[i]; + } + + // good output A + for (int i = 0; i < 4; i++) + { + udpSendData[182+i] = artPollReply.GoodOutputA[i]; + } + + // sw in + for (int i = 0; i < 4; i++) + { + udpSendData[186+i] = artPollReply.SwIn[i]; + } + + // sw out + for (int i = 0; i < 4; i++) + { + udpSendData[190+i] = artPollReply.SwOut[i]; + } + + // prio acn + udpSendData[194] = artPollReply.AcnPriority; + + // sw macro + udpSendData[195] = artPollReply.SwMacro; + + // sw remote + udpSendData[196] = artPollReply.SwRemote; + + // spare + for (int i = 0; i < 3; i++) + { + udpSendData[197+i] = 0x00; + } + + // style + udpSendData[200] = artPollReply.Style; + + // mac address + for (int i = 0; i < 6; i++) + { + udpSendData[201+i] = artPollReply.Mac[i]; + } + } + if(length >= 208) + { + // bind ip + for (int i = 0; i < 4; i++) + { + udpSendData[207+i] = artPollReply.BindIp[i]; + } + } + if(length >= 212) + { + // bind index + udpSendData[211] = artPollReply.BindIndex; + } + if(length >= 213) + { + // status 2 + udpSendData[212] = artPollReply.Status2; + } + if(length >= 214) + { + // good output b + for (int i = 0; i < 4; i++) + { + udpSendData[213+i] = artPollReply.GoodOutputB[i]; + } + } + if(length >= 218) + { + // status 3 + udpSendData[217] = artPollReply.Status3; + } + if(length >= 219) + { + // default responder uid + for (int i = 0; i < 6; i++) + { + udpSendData[218+i] = artPollReply.DefaulRespUID[i]; + } + } + if(length >= 225) + { + // user data + udpSendData[224] = artPollReply.UserHi; + udpSendData[225] = artPollReply.UserLo; + } + if(length >= 227) + { + // user data + udpSendData[226] = artPollReply.RefreshRateHi; + udpSendData[227] = artPollReply.RefreshRateLo; + + // filler + for (int i = 0; i < 8; i++) + { + udpSendData[228+i] = 0x00; + } + + } +} + +void ArtNetController::parseFromArtCommand(ArtCommand artCommand, uint8_t* udpSendData, int length) +{ + if(length > 16) + { + // id + for(int i = 0; i<8; i++) + { + udpSendData[i] = artCommand.Id[i]; + } + + // op code + udpSendData[8] = artCommand.OpCode; + udpSendData[9] = artCommand.OpCode >> 8; + + // protocol version + udpSendData[10] = artCommand.ProtVerHi; + udpSendData[11] = artCommand.ProtVerLo; + + // esta manifactur + udpSendData[12] = artCommand.EstaManHi; + udpSendData[13] = artCommand.EstaManLo; + + // length + udpSendData[14] = artCommand.Length >> 8; + udpSendData[15] = artCommand.Length; + + for(int i=0; i < artCommand.Length; i++) + { + udpSendData[16+i] = artCommand.Data[i]; + } + } +} + +void ArtNetController::parseFromArtDataReply(ArtDataReply artDataReply, uint8_t* udpSendData, int length) +{ + if(length >= 20) + { + // id + for(int i = 0; i<8; i++) + { + udpSendData[i] = artDataReply.Id[i]; + } + + // op code + udpSendData[8] = artDataReply.OpCode; + udpSendData[9] = artDataReply.OpCode >> 8; + + // protocol version + udpSendData[10] = artDataReply.ProtVerHi; + udpSendData[11] = artDataReply.ProtVerLo; + + // esta manifactur + udpSendData[12] = artDataReply.EstaManHi; + udpSendData[13] = artDataReply.EstaManLo; + + // oem + udpSendData[14] = artDataReply.OemHi; + udpSendData[15] = artDataReply.OemLo; + + // request code + udpSendData[16] = artDataReply.RequestCode >> 8; + udpSendData[17] = artDataReply.RequestCode; + + // peyload lengths + udpSendData[18] = artDataReply.PayLength >> 8; + udpSendData[19] = artDataReply.PayLength; + + for(int i=0; i < artDataReply.PayLength; i++) + { + udpSendData[20+i] = artDataReply.PayLoad[i]; + } + } +} + +void ArtNetController::sendArtPollReply() +{ + int packageSize = 236; + + ArtPollReply artPollReply; + // set controller ip + for(int i = 0; i < 4; i++) + { + artPollReply.IpAddress[i] = this->nodeIpAddress[i]; + } + + char* longName = "KIWI (is a) Illumination Weeny Interface"; + + // set long name + bool endOfLine = false; + for(int i = 0; i < 64; i++) + { + if(longName[i] != '\0' && !endOfLine) + { + artPollReply.LongName[i] = longName[i]; + } + else + { + endOfLine = true; + artPollReply.LongName[i] = 0x00; + } + } + + // set node report + for(int i = 0; i < 64; i++) + { + artPollReply.NodeReport[i] = 0x00; + } + + // set mac address + for(int i = 0; i < 6; i++) + { + artPollReply.Mac[i] = this->mac[i]; + } + + uint8_t updSendData[packageSize]; + this->parseFromArtPollReply(artPollReply, updSendData, packageSize); + + + IPAddress remoteIp = IPAddress(this->controllerIpAddress); + this->udp.beginPacket(remoteIp, UDP_PORT_ADRESS); + this->udp.write(updSendData, packageSize); + this->udp.endPacket(); +} + +void ArtNetController::sendArtCommand(char *message, uint16_t length) +{ + ArtCommand artCommand; + artCommand.Length = length; + artCommand.Data = message; + int packageSize = 16 + length; + + if(this->controllerIpAddress[0] != 0x00) + { + uint8_t updSendData[packageSize]; + this->parseFromArtCommand(artCommand, updSendData, packageSize); + + IPAddress remoteIp = IPAddress(this->controllerIpAddress); + this->udp.beginPacket(remoteIp, UDP_PORT_ADRESS); + this->udp.write(updSendData, packageSize); + this->udp.endPacket(); + } +} + +void ArtNetController::sendArtDataReply(uint16_t dataRequstCode) +{ + ArtDataReply artDataReply; + artDataReply.RequestCode = dataRequstCode; + int packageSize = 20; + uint8_t productUrl[PRODUCT_URL_LENGTHS]; + for(int i = 0; i < PRODUCT_URL_LENGTHS; i++) + { + productUrl[i] = String(PRODUCT_URL).charAt(i); + } + + switch(dataRequstCode) + { + case DR_POLL: + artDataReply.PayLength = 0x0000; + artDataReply.PayLoad = nullptr; + break; + case DR_URL_PRODUCT: + artDataReply.PayLength = PRODUCT_URL_LENGTHS; + artDataReply.PayLoad = productUrl; + packageSize += PRODUCT_URL_LENGTHS; + break; + case DR_URL_USER_GUIDE: + artDataReply.PayLength = PRODUCT_URL_LENGTHS; + artDataReply.PayLoad = productUrl; + packageSize += PRODUCT_URL_LENGTHS; + break; + case DR_URL_SUPPORT: + artDataReply.PayLength = PRODUCT_URL_LENGTHS; + artDataReply.PayLoad = productUrl; + packageSize += PRODUCT_URL_LENGTHS; + break; + case DR_URL_PERS_UDR: + artDataReply.PayLength = 0x0000; + artDataReply.PayLoad = nullptr; + break; + break; + case DR_URL_PERS_GDTF: + artDataReply.PayLength = 0x0000; + artDataReply.PayLoad = nullptr; + break; + break; + case DR_PRESET_STATUS: + artDataReply.PayLength = (uint16_t)NUMBER_OF_PRESETS; + artDataReply.PayLoad = getPresetStatus(); + packageSize += NUMBER_OF_PRESETS; + break; + } + + uint8_t updSendData[packageSize]; + this->parseFromArtDataReply(artDataReply, updSendData, packageSize); + + IPAddress remoteIp = IPAddress(this->controllerIpAddress); + this->udp.beginPacket(remoteIp, UDP_PORT_ADRESS); + this->udp.write(updSendData, packageSize); + this->udp.endPacket(); +} + +void ArtNetController::processArtCommand(uint8_t updData[], int length) +{ + ArtCommand artCommand = this->parseToArtCommand(updData, length); + + char *commandString = strtok(artCommand.Data, "&"); + while (commandString != NULL) { + String command = String(commandString).substring(0, String(commandString).indexOf('=')); + int commandValuelength = (String(commandString).substring(String(commandString).indexOf('=')+1)).length() + 1; + char commandValue[commandValuelength]; + String(commandString).substring(String(commandString).indexOf('=')+1).toCharArray(commandValue, commandValuelength); + deserializeJson(doc, commandValue, commandValuelength); + + if(command.equals(ART_COM_AP)) + { + this->presetRepository->activatePreset(doc[activatePresetStructure().presetId]); + } + else if(command.equals(ART_COM_DP)) + { + this->presetRepository->deactivatePreset(doc[deactivatePresetStructure().presetId]); + } + else if(command.equals(ART_COM_CP)) + { + this->presetRepository->clearPreset(doc[clearPresetStructure().presetId]); + } + else if(command.equals(ART_COM_SP)) + { + this->presetRepository->setPresetGroupId(doc[setPresetStructure().presetId], doc[setPresetStructure().groupId]); + this->presetRepository->setPresetChannelValue(doc[setPresetStructure().presetId], doc[setPresetStructure().channel], doc[setPresetStructure().value]); + } + else if(command.equals(ART_COM_SC)) + { + this->dmxController->setChannelStatus(doc[setChannelStateStructure().isActive], doc[setChannelStateStructure().channel]); + this->presetRepository->updatePresetState(); + } + else if(command.equals(ART_COM_AG)) + { + this->presetRepository->activateGroup(doc[activateGroupStructure().groupId]); + } + else if(command.equals(ART_COM_DG)) + { + this->presetRepository->deactivateGroup(doc[deactivateGroupStructure().groupId]); + } + + commandString = strtok(NULL, "&"); + } +} + +void ArtNetController::processArtDataRequest(uint8_t updData[], int length) +{ + ArtDataRequest artDataRequest = this->parseToArtDataRequest(updData, length); + this->sendArtDataReply(artDataRequest.RequestCode); +} + +void ArtNetController::processArtDmx(uint8_t updData[], int length) +{ + ArtDmx artDmx = this->parseToArtDmx(updData, length); + if(artDmx.SubUni == 0x00 && artDmx.Net == 0x00) + { + for(int i = 0; i<artDmx.Length; i++) + { + this->dmxController->setChannelValue(i+1, artDmx.Data[i]); + } + } +} \ No newline at end of file diff --git a/src/lightControlUnit/ArtNetController/ArtNetController.h b/src/lightControlUnit/ArtNetController/ArtNetController.h new file mode 100644 index 0000000..71c7d7c --- /dev/null +++ b/src/lightControlUnit/ArtNetController/ArtNetController.h @@ -0,0 +1,49 @@ +#ifndef ArtNetController_h +#define ArtNetController_h + +#include <SPI.h> +#include <Arduino.h> +#include <Ethernet.h> +#include <EthernetUdp.h> +#include <ArduinoJson.h> +#include "./ArtCommands.h" +#include "../config/config.h" +#include "./ArtNetPackageTypes.h" +#include "../DmxController/DmxController.h" +#include "../PresetRepository/PresetRepository.h" + +class ArtNetController +{ + private: + PresetRepository* presetRepository; + DmxController* dmxController; + uint8_t mac[6]; + uint8_t nodeIpAddress[4]; + uint8_t controllerIpAddress[4] = {0x00, 0x00, 0x00, 0x00}; + EthernetUDP udp; + int startUdp(); + bool checkArtNetHeader(uint8_t udpData[]); + uint8_t *getPresetStatus(); + StaticJsonDocument<50> doc; + + public: + ArtNetController(); + ArtNetController(DmxController* dmxController, PresetRepository* presetRepository); + void sendPresetActivated(int presetId); + void sendPresetDeactivated(int presetId); + void listenToArtNet(); + ArtPoll parseToArtPoll(uint8_t updData[], int length); + ArtCommand parseToArtCommand(uint8_t updData[], int length); + ArtDataRequest parseToArtDataRequest(uint8_t updData[], int length); + ArtDmx parseToArtDmx(uint8_t updData[], int length); + void parseFromArtPollReply(ArtPollReply artPollReply, uint8_t* udpSendData, int length); + void parseFromArtCommand(ArtCommand artCommand, uint8_t* udpSendData, int length); + void parseFromArtDataReply(ArtDataReply artDataReply, uint8_t* udpSendData, int length); + void sendArtPollReply(); + void sendArtCommand(char *message, uint16_t length); + void sendArtDataReply(uint16_t dataRequstCode); + void processArtCommand(uint8_t updData[], int length); + void processArtDataRequest(uint8_t updData[], int length); + void processArtDmx(uint8_t updData[], int length); +}; +#endif \ No newline at end of file diff --git a/src/lightControlUnit/ArtNetController/ArtNetPackageTypes.h b/src/lightControlUnit/ArtNetController/ArtNetPackageTypes.h new file mode 100644 index 0000000..a32927f --- /dev/null +++ b/src/lightControlUnit/ArtNetController/ArtNetPackageTypes.h @@ -0,0 +1,148 @@ +#include <Arduino.h> + +// op codes +#define OP_POLL 0x2000 +#define OP_POLL_REPLY 0x2100 +#define OP_COMMAND 0x2400 +#define OP_DATA_REQUEST 0x2700 +#define OP_DATA_REPLY 0x2800 +#define OP_DMX 0x5000 + +// request codes +#define DR_POLL 0x0000 +#define DR_URL_PRODUCT 0x0001 +#define DR_URL_USER_GUIDE 0x0002 +#define DR_URL_SUPPORT 0x0003 +#define DR_URL_PERS_UDR 0x0004 +#define DR_URL_PERS_GDTF 0x0005 +#define DR_PRESET_STATUS 0x8000 + +// artCommand commands +#define ART_COM_AP "activatePreset" +#define ART_COM_DP "deactivatePreset" +#define ART_COM_CP "clearPreset" +#define ART_COM_SP "setPreset" +#define ART_COM_SC "setChannelState" +#define ART_COM_AG "activateGroup" +#define ART_COM_DG "deactivateGroup" +#define ART_COM_PA "presetActivated" +#define ART_COM_PD "presetDeactivated" + +// information +#define PORT_NAME "KIWI" +#define LONG_NAME "KIWI (is a) Illumination Weeny Interface" + +struct ArtPoll +{ + uint8_t Id[8] = {'A','r','t','-','N','e','t',0x00}; + uint16_t OpCode = OP_POLL; + uint8_t ProtVerHi = 0x00; + uint8_t ProtVerLo = 0x0e; // Version 14 + uint8_t Flags = 0x00; + uint8_t DiagPriority = 0x00; + uint8_t TargetPortAddressTopHi = 0x00; + uint8_t TargetPortAddressTopLo = 0x00; + uint8_t TargetPortAddressBottomHi = 0x00; + uint8_t TargetPortAddressBottomLo = 0x00; + uint8_t EstaManHi = 0x00; + uint8_t EstaManLo = 0x00; + uint8_t OemHi = 0xff; + uint8_t OemLo = 0xff; +}; + +struct ArtPollReply +{ + uint8_t Id[8] = {'A','r','t','-','N','e','t',0x00}; + uint16_t OpCode = OP_POLL_REPLY; + uint8_t IpAddress[4] = {0x00, 0x00, 0x00, 0x00}; + uint16_t Port = 0x1936; + uint8_t VersInfoH = 0x00; + uint8_t VersInfoL = 0x01; + uint8_t NetSwitch = 0x00; + uint8_t SubSwitch = 0x00; + uint8_t OemHi = 0xff; + uint8_t OemLo = 0xff; + uint8_t UbeaVersion = 0x00; + uint8_t Status1 = 0x00; + uint8_t EstaManLo = 0x00; + uint8_t EstaManHi = 0x00; + uint8_t PortName[18] = {'K', 'I', 'W', 'I', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t LongName[64]; + uint8_t NodeReport[64]; + uint8_t NumPortsHi = 0x00; + uint8_t NumPortsLo = 0x01; + uint8_t PortTypes[4] = {0x80, 0x00, 0x00, 0x00}; + uint8_t GoodInput[4] = {0x04, 0x04, 0x04, 0x04}; + uint8_t GoodOutputA [4] = {0x80, 0x00, 0x00, 0x00}; + uint8_t SwIn[4] = {0x00, 0x00, 0x00, 0x00}; + uint8_t SwOut[4] = {0x00, 0x00, 0x00, 0x00}; + uint8_t AcnPriority = 0x00; + uint8_t SwMacro = 0x00; + uint8_t SwRemote = 0x00; + uint8_t Style = 0x00; + uint8_t Mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t BindIp[4] = {0x00, 0x00, 0x00, 0x00}; + uint8_t BindIndex = 0x00; + uint8_t Status2 = 0x00; + uint8_t GoodOutputB[4] = {0x00, 0x00, 0x00, 0x00}; + uint8_t Status3 = 0x00; + uint8_t DefaulRespUID[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t UserHi = 0x00; + uint8_t UserLo = 0x00; + uint8_t RefreshRateHi = 0x00; + uint8_t RefreshRateLo = 0x2C; +}; + +struct ArtCommand +{ + uint8_t Id[8] = {'A','r','t','-','N','e','t',0x00}; + uint16_t OpCode = OP_COMMAND; + uint8_t ProtVerHi = 0x00; + uint8_t ProtVerLo = 0x0e; // Version 14; + uint8_t EstaManHi = 0x00; + uint8_t EstaManLo = 0x00; + uint16_t Length = 0x0000; + char *Data; +}; + +struct ArtDataRequest +{ + uint8_t Id[8] = {'A','r','t','-','N','e','t',0x00}; + uint16_t OpCode = OP_DATA_REQUEST; + uint8_t ProtVerHi = 0x00; + uint8_t ProtVerLo = 0x0e; // Version 14; + uint8_t EstaManHi = 0x00; + uint8_t EstaManLo = 0x00; + uint8_t OemHi = 0xff; + uint8_t OemLo = 0xff; + uint16_t RequestCode = 0x0000; +}; + +struct ArtDataReply +{ + uint8_t Id[8] = {'A','r','t','-','N','e','t',0x00}; + uint16_t OpCode = OP_DATA_REPLY; + uint8_t ProtVerHi = 0x00; + uint8_t ProtVerLo = 0x0e; // Version 14; + uint8_t EstaManHi = 0x00; + uint8_t EstaManLo = 0x00; + uint8_t OemHi = 0xff; + uint8_t OemLo = 0xff; + uint16_t RequestCode = 0x0000; + uint16_t PayLength = 0x0000; + uint8_t *PayLoad; +}; + +struct ArtDmx +{ + uint8_t Id[8] = {'A','r','t','-','N','e','t',0x00}; + uint16_t OpCode = OP_DMX; + uint8_t ProtVerHi = 0x00; + uint8_t ProtVerLo = 0x0e; // Version 14; + uint8_t Sequence = 0x00; + uint8_t Physical = 0x00; + uint8_t SubUni = 0x00; + uint8_t Net = 0x00; + uint16_t Length = 0x0200; + uint8_t *Data; +}; \ No newline at end of file diff --git a/src/lightControlUnit/DmxChannel/DmxChannel.h b/src/lightControlUnit/DmxChannel/DmxChannel.h new file mode 100644 index 0000000..4363f69 --- /dev/null +++ b/src/lightControlUnit/DmxChannel/DmxChannel.h @@ -0,0 +1,6 @@ +struct DmxChannel +{ + public: + bool isUsed; + uint8_t value; +}; \ No newline at end of file diff --git a/src/lightControlUnit/DmxController/DmxController.cpp b/src/lightControlUnit/DmxController/DmxController.cpp new file mode 100644 index 0000000..b65c520 --- /dev/null +++ b/src/lightControlUnit/DmxController/DmxController.cpp @@ -0,0 +1,36 @@ +#include "DmxController.h" + +DmxController::DmxController() +{ + DmxSimple.usePin(DMX_PINOUT); + DmxSimple.maxChannel(MAX_DMX_CHANNEL); + activChannel = Set(true); +} + +void DmxController::setChannelValue(int channel, unsigned char value) +{ + DmxSimple.write(channel, value); +} + +void DmxController::setChannelValue(bool isActive, int channel, unsigned char value) +{ + this->setChannelStatus(isActive, channel); + this->setChannelValue(channel, value); +} + +void DmxController::setChannelStatus(bool isActive, int channel) +{ + if(isActive) + { + this->activChannel.add(channel); + } + else + { + this->activChannel.sub(channel); + } +} + +Set DmxController::getActiveChannel() +{ + return this->activChannel; +} \ No newline at end of file diff --git a/src/lightControlUnit/DmxController/DmxController.h b/src/lightControlUnit/DmxController/DmxController.h new file mode 100644 index 0000000..ae57d11 --- /dev/null +++ b/src/lightControlUnit/DmxController/DmxController.h @@ -0,0 +1,21 @@ +#ifndef DmxController_h +#define DmxController_h +#include <DmxSimple.h> +#include "set.h" +#include "../config/config.h" + +class DmxController +{ + private: + unsigned char dmxOut[MAX_DMX_CHANNEL]; + Set activChannel; + + + public: + DmxController(); + void setChannelValue(int channel, unsigned char value); + void setChannelValue(bool isActive, int channel, unsigned char value); + void setChannelStatus(bool isActive, int channel); + Set getActiveChannel(); +}; +#endif \ No newline at end of file diff --git a/src/lightControlUnit/Preset/Preset.cpp b/src/lightControlUnit/Preset/Preset.cpp new file mode 100644 index 0000000..eb30fbe --- /dev/null +++ b/src/lightControlUnit/Preset/Preset.cpp @@ -0,0 +1,131 @@ +#include "Preset.h" + +Preset::Preset() {} + +Preset::Preset(int eepromAdress, PresetLed presetLed) +{ + this->eepromAdress = eepromAdress; + this->presetLed = presetLed; + this->clearPreset(); + this->usedChannel = Set(true); +} + +void Preset::loadPresetData() +{ + PresetStoredInfromation data; + EEPROM.get(this->eepromAdress, data); + this->setState(data.state); + this->setGroupId(data.groupId); + for(int i = 0; i < MAX_DMX_CHANNEL; i++) + { + this->dmxChannels[i] = data.dmxChannels[i]; + if(data.dmxChannels[i].isUsed) + { + this->usedChannel.add(i+1); + } + } +} + +void Preset::savePresetData() +{ + PresetStoredInfromation dataWrite; + dataWrite.state = this->state; + dataWrite.groupId = this->groupId; + for (uint8_t i = 0; i < 100; i++) + { + dataWrite.dmxChannels[i] = this->dmxChannels[i]; + } + EEPROM.put(this->eepromAdress, dataWrite); +} + +void Preset::setState(uint8_t state) +{ + this->state = state; + switch(state) + { + case P_DEAKTIV: + this->presetLed.setColor(false, false, false); + break; + case P_ACTIVE: + this->presetLed.setColor(false, true, false); + break; + case P_PART_ACTIVE: + this->presetLed.setColor(false, false, true); + break; + } +} + +void Preset::setChannelValue(int channel, uint8_t value) +{ + DmxChannel dmxChannel = {true, value}; + if(channel <= MAX_DMX_CHANNEL) + { + this->usedChannel.add(channel); + this->dmxChannels[channel-1] = dmxChannel; + } +} + +void Preset::setGroupId(uint8_t groupId) +{ + this->groupId = groupId; +} + +uint8_t Preset::getState() +{ + return this->state; +} + +void Preset::clearPreset() +{ + this->usedChannel.clear(); + for(int i = 0; i<MAX_DMX_CHANNEL; i++) + { + DmxChannel emptyChannel = {false, 0}; + this->dmxChannels[i] = emptyChannel; + } + this->groupId = DEFAULT_GROUP_ID; +} + +void Preset::updateState(Set activeChannel, bool aktiveGroupMember) +{ + bool isNonActive = false; + bool isPartActive = false; + int channel; + + for(int i = 0; i < this->usedChannel.count(); i++) { + channel = i==0 ? this->usedChannel.first() : this->usedChannel.next(); + + if(activeChannel.has(channel)) + { + isPartActive = true; + } + else + { + isNonActive = true; + } + } + + if (isNonActive && !isPartActive) + { + this->setState(P_DEAKTIV); + } + else if (isNonActive && isPartActive) + { + this->setState(P_PART_ACTIVE); + } + else if(isPartActive && this->state != P_ACTIVE && !aktiveGroupMember) + { + this->setState(P_PART_ACTIVE); + } + this->savePresetData(); +} + +uint8_t Preset::getGroupId() +{ + return this->groupId; +} + +DmxChannel* Preset::getDmxChannel() +{ + return this->dmxChannels; +} \ No newline at end of file diff --git a/src/lightControlUnit/Preset/Preset.h b/src/lightControlUnit/Preset/Preset.h new file mode 100644 index 0000000..24b1132 --- /dev/null +++ b/src/lightControlUnit/Preset/Preset.h @@ -0,0 +1,33 @@ +#ifndef Preset_h +#define Preset_h + +#include <Arduino.h> +#include <EEPROM.h> +#include "set.h" +#include "../config/config.h" +#include "../PresetStoredInfromation/PresetStoredInfromation.h" +#include "../PresetLed/PresetLed.h" + +class Preset:PresetStoredInfromation +{ + private: + int eepromAdress; + PresetLed presetLed; + Set usedChannel; + + public: + Preset(); + Preset(int eepromAdress, PresetLed presetLed); + uint8_t getState(); + uint8_t getGroupId(); + DmxChannel* getDmxChannel(); + void setState(uint8_t state); + void setGroupId(uint8_t groupId); + void setChannelValue(int channel, uint8_t value); + void clearPreset(); + void loadPresetData(); + void savePresetData(); + void updateState(Set activeChannel, bool aktiveGroupMember); + +}; +#endif \ No newline at end of file diff --git a/src/lightControlUnit/PresetLed/PresetLed.cpp b/src/lightControlUnit/PresetLed/PresetLed.cpp new file mode 100644 index 0000000..0698af6 --- /dev/null +++ b/src/lightControlUnit/PresetLed/PresetLed.cpp @@ -0,0 +1,20 @@ +#include "PresetLed.h" + +PresetLed::PresetLed(){} + +PresetLed::PresetLed(int redPinOut, int greenPinOut, int bluePinOut) +{ + this->redPinOut = redPinOut; + this->greenPinOut = greenPinOut; + this->bluePinOut = bluePinOut; + + pinMode(this->redPinOut, OUTPUT); + pinMode(this->greenPinOut, OUTPUT); + pinMode(this->bluePinOut, OUTPUT); +} + +void PresetLed::setColor(bool red, bool green, bool blue) { + digitalWrite(this->redPinOut, red ? HIGH: LOW); + digitalWrite(this->greenPinOut, green ? HIGH: LOW); + digitalWrite(this->bluePinOut, blue ? HIGH: LOW); +} \ No newline at end of file diff --git a/src/lightControlUnit/PresetLed/PresetLed.h b/src/lightControlUnit/PresetLed/PresetLed.h new file mode 100644 index 0000000..95ed7f3 --- /dev/null +++ b/src/lightControlUnit/PresetLed/PresetLed.h @@ -0,0 +1,17 @@ +#ifndef PresetLed_h +#define PresetLed_h +#include <Arduino.h> + +class PresetLed +{ + private: + int redPinOut; + int greenPinOut; + int bluePinOut; + + public: + PresetLed(); + PresetLed(int redPinOut, int greenPinOut, int bluePinOut); + void setColor(bool red, bool green, bool blue); +}; +#endif \ No newline at end of file diff --git a/src/lightControlUnit/PresetRepository/PresetRepository.cpp b/src/lightControlUnit/PresetRepository/PresetRepository.cpp new file mode 100644 index 0000000..9577f67 --- /dev/null +++ b/src/lightControlUnit/PresetRepository/PresetRepository.cpp @@ -0,0 +1,171 @@ +#include "PresetRepository.h" +#include "../PresetLed/PresetLed.cpp" +#include "../Preset/Preset.cpp" +#include "../config/config.h" +#include <Arduino.h> + +PresetRepository::PresetRepository(){} + +PresetRepository::PresetRepository(int eepromStartAdress, DmxController* dmxController, int** leds) +{ + this->eepromStartAdress = eepromStartAdress; + this->dmxController = dmxController; + + for(int i = 0; i<NUMBER_OF_PRESETS; i++) + { + this->presets[i] = Preset(eepromStartAdress + (i*sizeof(PresetStoredInfromation)), PresetLed(leds[i][0], leds[i][1], leds[i][2])); + this->presets[i].loadPresetData(); + if(this->presets[i].getState() == P_ACTIVE) { + this->sendDmxValuesOfPreset(i); + } + } +} + +void PresetRepository::sendDmxValuesOfPreset(int presetId) +{ + for(int i = 0; i<MAX_DMX_CHANNEL; i++) + { + if(this->presets[presetId].getDmxChannel()[i].isUsed) + { + this->dmxController->setChannelValue(true, 1+i, this->presets[presetId].getDmxChannel()[i].value); + } + } +} + +void PresetRepository::disableOtherGroupPresets(int presetId) +{ + for(int i = 0; i<NUMBER_OF_PRESETS; i++) { + if((i != presetId) && (this->presets[i].getGroupId() == this->presets[presetId].getGroupId())) + { + this->setPresetDeactiv(i); + } + } +} + +bool PresetRepository::isGroupMemberActive(uint8_t groupId) +{ + bool isActive = false; + for(int i = 0; i < NUMBER_OF_PRESETS; i++) + { + if(this->presets[i].getGroupId() == groupId && this->presets[i].getState() == P_ACTIVE) + { + isActive = true; + } + } + + return isActive; +} + +void PresetRepository::updatePresetState() +{ + for(int i = 0; i < NUMBER_OF_PRESETS; i++) + { + this->presets[i].updateState(this->dmxController->getActiveChannel(), this->isGroupMemberActive(this->presets[i].getGroupId())); + } +} + +void PresetRepository::updatePresetState(uint8_t groupId) +{ + for(int i = 0; i < NUMBER_OF_PRESETS; i++) + { + if(this->presets[i].getGroupId() != groupId) + { + this->presets[i].updateState(this->dmxController->getActiveChannel(), this->isGroupMemberActive(this->presets[i].getGroupId())); + } + } +} + +void PresetRepository::activatePreset(int presetId) +{ + this->setPresetActive(presetId); + this->disableOtherGroupPresets(presetId); + this->sendDmxValuesOfPreset(presetId); + this->updatePresetState(this->presets[presetId].getGroupId()); +} + +void PresetRepository::deactivatePreset(int presetId) +{ + this->setPresetDeactiv(presetId); + for(int i = 0; i<MAX_DMX_CHANNEL; i++) + { + if(this->presets[presetId].getDmxChannel()[i].isUsed) + { + this->dmxController->setChannelValue(false, 1+i, 0); + } + } + this->updatePresetState(this->presets[presetId].getGroupId()); + this->disableOtherGroupPresets(presetId); +} + +void PresetRepository::setPresetActive(int presetId) +{ + this->presets[presetId].setState(P_ACTIVE); + this->presets[presetId].savePresetData(); +} + +void PresetRepository::setPresetDeactiv(int presetId) +{ + this->presets[presetId].setState(P_DEAKTIV); + this->presets[presetId].savePresetData(); +} + +void PresetRepository::activateGroup(uint8_t groupId) +{ + for(int i = 0; i<NUMBER_OF_PRESETS; i++) + { + if(this->presets[i].getGroupId() == groupId) + { + this->presets[i].setState(P_PART_ACTIVE); + this->presets[i].savePresetData(); + } + } +} + +void PresetRepository::deactivateGroup(uint8_t groupId) +{ + for(int i = 0; i<NUMBER_OF_PRESETS; i++) + { + if(this->presets[i].getGroupId() == groupId) + { + this->deactivatePreset(i); + } + } +} + +void PresetRepository::setPresetChannelValue(int presetId, int channel, uint8_t value) +{ + this->presets[presetId].setChannelValue(channel, value); + if(this->presets[presetId].getState() == P_ACTIVE) + { + this->sendDmxValuesOfPreset(presetId); + this->updatePresetState(this->presets[presetId].getGroupId()); + } + this->presets[presetId].savePresetData(); +} + +void PresetRepository::setPresetGroupId(int presetId, uint8_t groupId) +{ + this->presets[presetId].setGroupId(groupId); + this->presets[presetId].savePresetData(); + if(this->presets[presetId].getState() == 1) + { + this->disableOtherGroupPresets(presetId); + } +} + +void PresetRepository::clearPreset(int presetId) +{ + this->deactivatePreset(presetId); + this->presets[presetId].clearPreset(); + this->presets[presetId].savePresetData(); +} + +uint8_t PresetRepository::getPresetState(int presetId) +{ + return this->presets[presetId].getState(); +} + +Preset* PresetRepository::getPresets() +{ + return this->presets; +} \ No newline at end of file diff --git a/src/lightControlUnit/PresetRepository/PresetRepository.h b/src/lightControlUnit/PresetRepository/PresetRepository.h new file mode 100644 index 0000000..9301cdf --- /dev/null +++ b/src/lightControlUnit/PresetRepository/PresetRepository.h @@ -0,0 +1,34 @@ +#ifndef PresetRepository_h +#define PresetRepository_h + +#include "../Preset/Preset.h" +#include "../DmxController/DmxController.h" + +class PresetRepository +{ + private: + Preset presets[6]; + DmxController* dmxController; + int eepromStartAdress; + void sendDmxValuesOfPreset(int presetId); + void disableOtherGroupPresets(int presetId); + bool isGroupMemberActive(uint8_t groupId); + + public: + PresetRepository(); + PresetRepository(int eepromStartAdress, DmxController* dmxController, int** leds); + void activatePreset(int presetId); + void deactivatePreset(int presetId); + void setPresetActive(int presetId); + void setPresetDeactiv(int presetId); + void activateGroup(uint8_t groupId); + void deactivateGroup(uint8_t groupId); + void setPresetChannelValue(int presetId, int channel, uint8_t value); + void setPresetGroupId(int presetId, uint8_t groupId); + void clearPreset(int presetId); + void updatePresetState(); + void updatePresetState(uint8_t groupId); + uint8_t getPresetState(int presetId); + Preset* getPresets(); +}; +#endif \ No newline at end of file diff --git a/src/lightControlUnit/PresetStoredInfromation/PresetStoredInfromation.h b/src/lightControlUnit/PresetStoredInfromation/PresetStoredInfromation.h new file mode 100644 index 0000000..c610199 --- /dev/null +++ b/src/lightControlUnit/PresetStoredInfromation/PresetStoredInfromation.h @@ -0,0 +1,9 @@ +#include "../DmxChannel/DmxChannel.h" + +struct PresetStoredInfromation +{ + public: + uint8_t state; + uint8_t groupId; + DmxChannel dmxChannels[100]; +}; \ No newline at end of file diff --git a/src/lightControlUnit/config/config.h b/src/lightControlUnit/config/config.h new file mode 100644 index 0000000..355fd92 --- /dev/null +++ b/src/lightControlUnit/config/config.h @@ -0,0 +1,68 @@ +// global values +#define MAX_DMX_CHANNEL 100 +#define NUMBER_OF_PRESETS 6 +#define NUMBER_OF_LED_COLORS 3 +#define EEPROM_START_ADRESS 0 +#define PRODUCT_URL "https://gitlab.cvh-server.de/sboettger/kiwi-home-light-control-system/" +#define PRODUCT_URL_LENGTHS 70 + +// Art-Net addresses +#define ART_NET_NET_ADDRESS 0 +#define ART_NET_SUB_NET_ADDRESS 0 +#define ART_NET_UNIVERSE 0 + +// led pins +#define LED_1_PINOUT_R 46 +#define LED_1_PINOUT_G 44 +#define LED_1_PINOUT_B 48 + +#define LED_2_PINOUT_R 40 +#define LED_2_PINOUT_G 38 +#define LED_2_PINOUT_B 42 + +#define LED_3_PINOUT_R 47 +#define LED_3_PINOUT_G 45 +#define LED_3_PINOUT_B 49 + +#define LED_4_PINOUT_R 41 +#define LED_4_PINOUT_G 39 +#define LED_4_PINOUT_B 43 + +#define LED_5_PINOUT_R 35 +#define LED_5_PINOUT_G 33 +#define LED_5_PINOUT_B 37 + +#define LED_6_PINOUT_R 29 +#define LED_6_PINOUT_G 27 +#define LED_6_PINOUT_B 31 + +// buttons pins +#define BUTTON_1_PININ 36 +#define BUTTON_2_PININ 34 +#define BUTTON_3_PININ 32 +#define BUTTON_4_PININ 30 +#define BUTTON_5_PININ 28 +#define BUTTON_6_PININ 26 + +// dmx pins +#define DMX_PINOUT 24 + +// preset state values +#define P_DEAKTIV 0 +#define P_ACTIVE 1 +#define P_PART_ACTIVE 2 + +// common group id values +#define DEFAULT_GROUP_ID 0 + +// ethernet communication +#define MAC_ADDRESS_PART_01 0xA8 +#define MAC_ADDRESS_PART_02 0x61 +#define MAC_ADDRESS_PART_03 0x0A +#define MAC_ADDRESS_PART_04 0xAE +#define MAC_ADDRESS_PART_05 0xE0 +#define MAC_ADDRESS_PART_06 0x13 + +#define UDP_PORT_ADRESS 6454 +#define UPD_MAX_PACKAGE_LENGTH_READ 530 +#define UPD_MAX_PACKAGE_LENGTH_WRITE 532 \ No newline at end of file diff --git a/src/lightControlUnit/lightControlUnit.ino b/src/lightControlUnit/lightControlUnit.ino new file mode 100644 index 0000000..6aced8b --- /dev/null +++ b/src/lightControlUnit/lightControlUnit.ino @@ -0,0 +1,125 @@ +// This is the main file of the light controll unit. + +// includes +#include <Arduino.h> +#include <DmxSimple.h> +#include "./config/config.h" +#include "./DmxController/DmxController.cpp" +#include "./PresetRepository/PresetRepository.cpp" +#include "./ArtNetController/ArtNetController.cpp" + + + +// pin values +int led1[] = {LED_1_PINOUT_R, LED_1_PINOUT_G, LED_1_PINOUT_B}; +int led2[] = {LED_2_PINOUT_R, LED_2_PINOUT_G, LED_2_PINOUT_B}; +int led3[] = {LED_3_PINOUT_R, LED_3_PINOUT_G, LED_3_PINOUT_B}; +int led4[] = {LED_4_PINOUT_R, LED_4_PINOUT_G, LED_4_PINOUT_B}; +int led5[] = {LED_5_PINOUT_R, LED_5_PINOUT_G, LED_5_PINOUT_B}; +int led6[] = {LED_6_PINOUT_R, LED_6_PINOUT_G, LED_6_PINOUT_B}; + +int** leds = new int*[NUMBER_OF_PRESETS]; +int buttons[] = {BUTTON_1_PININ, BUTTON_2_PININ, BUTTON_3_PININ, BUTTON_4_PININ, BUTTON_5_PININ, BUTTON_6_PININ}; + +// controller and repositories +DmxController dmxController = DmxController(); +PresetRepository presetRepository; +ArtNetController artNetController; + + + +// setup functions +void setupLedArray() +{ + leds[0] = led1; + leds[1] = led2; + leds[2] = led3; + leds[3] = led4; + leds[4] = led5; + leds[5] = led6; +} + +void setupButtons() +{ + for(int i = 0; i < NUMBER_OF_PRESETS; i++){ + pinMode(buttons[i], INPUT_PULLUP); + } +} + +void setupPresetValues(int presetId, int groupId, int startDmxAdress, uint8_t r, uint8_t g, uint8_t b, uint8_t i, uint8_t f) +{ + presetRepository.setPresetChannelValue(presetId,startDmxAdress,r); + presetRepository.setPresetChannelValue(presetId,1+startDmxAdress,g); + presetRepository.setPresetChannelValue(presetId,2+startDmxAdress,b); + presetRepository.setPresetChannelValue(presetId,3+startDmxAdress,i); + presetRepository.setPresetChannelValue(presetId,4+startDmxAdress,f); + presetRepository.setPresetGroupId(presetId, groupId); +} + +void setupPresetValues() +{ + presetRepository.clearPreset(0); + presetRepository.clearPreset(1); + presetRepository.clearPreset(2); + presetRepository.clearPreset(3); + presetRepository.clearPreset(4); + presetRepository.clearPreset(5); + + setupPresetValues(0,1,1,255,0,0,255,0); // fixture 1 blue + setupPresetValues(0,1,6,255,0,0,255,0); // fixture 2 blue + + setupPresetValues(1,1,1,0,255,0,255,0); // fixture 1 green + setupPresetValues(1,1,6,0,255,0,255,0); // fixture 2 green + + setupPresetValues(2,2,11,255,255,255,100,0); // fixture 3 white + + setupPresetValues(3,3,16,255,0,0,255,0); // fixture 4 red + + setupPresetValues(4,4,1,0,255,0,255,0); // fixture 1 green + setupPresetValues(4,4,6,0,255,0,255,0); // fixture 2 green + setupPresetValues(4,4,11,0,255,0,255,0); // fixture 3 green + setupPresetValues(4,4,16,0,255,0,255,0); // fixture 4 green + + setupPresetValues(5,4,1,255,255,255,255,0); // fixture 1 white + setupPresetValues(5,4,6,255,255,255,255,0); // fixture 2 white + setupPresetValues(5,4,11,255,255,255,255,0); // fixture 3 white + setupPresetValues(5,4,16,255,255,255,255,0); // fixture 4 white + +} + +// check functinos +void checkButtonIsPressed() +{ + for(int i = 0; i < NUMBER_OF_PRESETS; i++) { + if(digitalRead(buttons[i]) == LOW) { + if(presetRepository.getPresetState(i)) + { + presetRepository.deactivatePreset(i); + artNetController.sendPresetDeactivated(i); + } + else + { + presetRepository.activatePreset(i); + artNetController.sendPresetActivated(i); + } + while(digitalRead(buttons[i]) == LOW){} + } + } +} + +void setup() +{ + Serial.begin(250000); + setupLedArray(); + setupButtons(); + + presetRepository = PresetRepository(EEPROM_START_ADRESS, &dmxController, leds); + artNetController = ArtNetController(&dmxController, &presetRepository); + //setupPresetValues(); +} + +void loop() +{ + checkButtonIsPressed(); + artNetController.listenToArtNet(); +} -- GitLab