From b4b55985f10728a9a8cf0f232e05fd6016a58305 Mon Sep 17 00:00:00 2001 From: Sal Choueib Date: Thu, 18 Oct 2018 21:35:53 +0100 Subject: [PATCH] ENH: Initial VR home widget implementation This commit is intended to implement features described in KitwareMedical/SlicerVirtualReality#43 by integrating changes originally associated with the following branches: * dgmato/gui-widgets-interactions - initially developed between 2021-12-15 and 2022-05-23 by @dgmato with contributions from @cpinter. - based on the "gui-widgets-module" branch * dgmato/gui-widgets-module - initially developed between 2018-10-18 and 2021-10-0523 by @cpinter with contributions from @dgmato, @lassoan, @SalehChoueib and @mohammadrashid0917 - with first commit titled "ENH: Initial VR home widget implementation" based of "dgmato/virtual-widget" branch. * dgmato/virtual-widget - initial commit contributed by @SalehChoueib on 2018-10-18 (ENH: Add virtual widget from arbitrary Qt Widget !WIP!) - follow-up development by @cpinter and @mohammadrashid0917 between 2019-05-13 and 2019-06-13 ------------ Commits originally associated with branch "dgmato/gui-widgets-interactions": * ENH: Remove unused UI widget in GUI Widgets module * ENH: Do not load avatar models when setting up VR pointer * ENH: Add new button to set up interaction Set up will include getting the VR controller transform node and applying that transform to the pointer model. * ENH: Add GUIWidgets module to Virtual Reality category for easy access * ENH: Load avatar models and transform pointer by right controller transform * ENH: Reorganize code and remove unused code * COMP: Fix build error due to Slicer API changes * ENH: Add new module target library vtkSlicerMarkupsModuleMRMLDisplayableManager * ENH: Send mouse release event to complete simulated mouse click after press event * ENH: Modify coordinate system for GUI widget Origin is located on the top left corner in Qt widgets. * ENH: Add button "Start interaction" to simulate interaction with VR pointer * ENH: Add module to simulate VR pointer display and movement ------------ Commits originally associated with branch "dgmato/gui-widgets-module": * ENH: Add buttons in GUIWidgets to show VR widgets; Fix include dirs variable in CMake * ENH: Add GUIWidgets module that provides in-VR Qt widget This is still a prototype, a few crucial features are needed: - Handle mouse interactions on the widget: forward events to Qt - Support moving the widget to arbitrary position - Implement outline tube and handle to grab with the controller - Option to keep widget floating in certain position relative to head motion (~HUD) - Implement laser pointer and use the intersection with these widgets as mouse interaction See more history: https://github.com/cpinter/VtkQWidgetTest * ENH: Creating and registering VR segment editor widget Creates and registers the VR segment editor widget (which can be switched to from the VR home widget) * BUG: Fix issues in VR home widget and the data widget * ENH: Creating and registering VR data module widget Creates and registers the VR data module widget (which can be switched to from the VR home widget) * ENH: Convert the interactor style to use the new vtkMRMLViewInteractorStyle Fix build error with Slicer-4.10 * STYLE: Add function documentation * ENH: Initial VR home widget implementation Squashed commits (originally associated with branch "dgmato/virtual-widget") * ENH: Create framework for registering VR modules Adds registerModule function to qMRMLVRView which can be called to create a new button for that module on the Modules bar in the VR home widget * ENH: Make VR home widget controls functional These changes make it so that changes in the module widget (viewable in Slicer) are reflected in the home widget (viewable in VR) and vice versa * ENH: Add VR optimized Qt Style Sheet This style sheet makes the text in the VR menu widget bigger, and gives the buttons and slider handles different colors for unpressed, hover, and pressed statuses. * ENH: Add VR home widget WIP Its UI is more or less complete but events are not handled and style sheet is needed * ENH: Add virtual widget from arbitrary Qt Widget !WIP! Co-authored-by: Andras Lasso Co-authored-by: Csaba Pinter Co-authored-by: David Garcia Mato Co-authored-by: Mohammad Rashid Co-authored-by: Sal Choueib --- CMakeLists.txt | 2 + GUIWidgets/CMakeLists.txt | 81 + GUIWidgets/Logic/CMakeLists.txt | 27 + GUIWidgets/Logic/vtkSlicerGUIWidgetsLogic.cxx | 143 ++ GUIWidgets/Logic/vtkSlicerGUIWidgetsLogic.h | 63 + GUIWidgets/MRML/CMakeLists.txt | 29 + .../MRML/vtkMRMLGUIWidgetDisplayNode.cxx | 35 + GUIWidgets/MRML/vtkMRMLGUIWidgetDisplayNode.h | 59 + GUIWidgets/MRML/vtkMRMLGUIWidgetNode.cxx | 140 ++ GUIWidgets/MRML/vtkMRMLGUIWidgetNode.h | 85 ++ GUIWidgets/MRMLDM/CMakeLists.txt | 47 + .../vtkMRMLGUIWidgetsDisplayableManager.cxx | 1319 +++++++++++++++++ .../vtkMRMLGUIWidgetsDisplayableManager.h | 177 +++ GUIWidgets/Resources/Icons/GUIWidgets.png | Bin 0 -> 20628 bytes .../UI/qSlicerGUIWidgetsModuleWidget.ui | 158 ++ .../Resources/qSlicerGUIWidgetsModule.qrc | 5 + GUIWidgets/Testing/CMakeLists.txt | 1 + GUIWidgets/Testing/Cxx/CMakeLists.txt | 17 + GUIWidgets/VTKWidgets/CMakeLists.txt | 34 + .../vtkSlicerQWidgetRepresentation.cxx | 274 ++++ .../vtkSlicerQWidgetRepresentation.h | 132 ++ .../VTKWidgets/vtkSlicerQWidgetTexture.cxx | 126 ++ .../VTKWidgets/vtkSlicerQWidgetTexture.h | 95 ++ .../VTKWidgets/vtkSlicerQWidgetWidget.cxx | 121 ++ .../VTKWidgets/vtkSlicerQWidgetWidget.h | 103 ++ GUIWidgets/Widgets/CMakeLists.txt | 38 + GUIWidgets/qSlicerGUIWidgetsModule.cxx | 157 ++ GUIWidgets/qSlicerGUIWidgetsModule.h | 77 + GUIWidgets/qSlicerGUIWidgetsModuleWidget.cxx | 403 +++++ GUIWidgets/qSlicerGUIWidgetsModuleWidget.h | 76 + PointerSimulator/CMakeLists.txt | 31 + PointerSimulator/PointerSimulator.py | 367 +++++ .../Resources/Icons/PointerSimulator.png | Bin 0 -> 21024 bytes .../Resources/UI/PointerSimulator.ui | 87 ++ PointerSimulator/Testing/CMakeLists.txt | 1 + .../Testing/Python/CMakeLists.txt | 2 + .../PointerSimulator.cpython-36.pyc | Bin 0 -> 9294 bytes VirtualReality/Logic/CMakeLists.txt | 2 +- VirtualReality/MRML/CMakeLists.txt | 2 +- .../Resources/menuTextureImage2.png | Bin 0 -> 2026761 bytes .../SubjectHierarchyPlugins/CMakeLists.txt | 2 +- VirtualReality/Widgets/CMakeLists.txt | 21 +- .../Resources/StyleSheets/VrWidgetStyle.qss | 50 + .../UI/qMRMLVirtualRealityDataModuleWidget.ui | 69 + .../UI/qMRMLVirtualRealityHomeWidget.ui | 208 +++ .../qMRMLVirtualRealitySegmentEditorWidget.ui | 79 + .../qSlicerVirtualRealityModuleWidgets.qrc | 5 + .../qMRMLVirtualRealityDataModuleWidget.cxx | 90 ++ .../qMRMLVirtualRealityDataModuleWidget.h | 68 + .../Widgets/qMRMLVirtualRealityHomeWidget.cxx | 423 ++++++ .../Widgets/qMRMLVirtualRealityHomeWidget.h | 101 ++ ...qMRMLVirtualRealitySegmentEditorWidget.cxx | 118 ++ .../qMRMLVirtualRealitySegmentEditorWidget.h | 67 + .../Widgets/qMRMLVirtualRealityView.cxx | 39 +- .../Widgets/qMRMLVirtualRealityView.h | 14 +- .../Widgets/qMRMLVirtualRealityView_p.h | 5 + VirtualReality/qSlicerHomeVirtualWidget.cxx | 93 ++ VirtualReality/qSlicerHomeVirtualWidget.h | 43 + 58 files changed, 6003 insertions(+), 8 deletions(-) create mode 100644 GUIWidgets/CMakeLists.txt create mode 100644 GUIWidgets/Logic/CMakeLists.txt create mode 100644 GUIWidgets/Logic/vtkSlicerGUIWidgetsLogic.cxx create mode 100644 GUIWidgets/Logic/vtkSlicerGUIWidgetsLogic.h create mode 100644 GUIWidgets/MRML/CMakeLists.txt create mode 100644 GUIWidgets/MRML/vtkMRMLGUIWidgetDisplayNode.cxx create mode 100644 GUIWidgets/MRML/vtkMRMLGUIWidgetDisplayNode.h create mode 100644 GUIWidgets/MRML/vtkMRMLGUIWidgetNode.cxx create mode 100644 GUIWidgets/MRML/vtkMRMLGUIWidgetNode.h create mode 100644 GUIWidgets/MRMLDM/CMakeLists.txt create mode 100644 GUIWidgets/MRMLDM/vtkMRMLGUIWidgetsDisplayableManager.cxx create mode 100644 GUIWidgets/MRMLDM/vtkMRMLGUIWidgetsDisplayableManager.h create mode 100644 GUIWidgets/Resources/Icons/GUIWidgets.png create mode 100644 GUIWidgets/Resources/UI/qSlicerGUIWidgetsModuleWidget.ui create mode 100644 GUIWidgets/Resources/qSlicerGUIWidgetsModule.qrc create mode 100644 GUIWidgets/Testing/CMakeLists.txt create mode 100644 GUIWidgets/Testing/Cxx/CMakeLists.txt create mode 100644 GUIWidgets/VTKWidgets/CMakeLists.txt create mode 100644 GUIWidgets/VTKWidgets/vtkSlicerQWidgetRepresentation.cxx create mode 100644 GUIWidgets/VTKWidgets/vtkSlicerQWidgetRepresentation.h create mode 100644 GUIWidgets/VTKWidgets/vtkSlicerQWidgetTexture.cxx create mode 100644 GUIWidgets/VTKWidgets/vtkSlicerQWidgetTexture.h create mode 100644 GUIWidgets/VTKWidgets/vtkSlicerQWidgetWidget.cxx create mode 100644 GUIWidgets/VTKWidgets/vtkSlicerQWidgetWidget.h create mode 100644 GUIWidgets/Widgets/CMakeLists.txt create mode 100644 GUIWidgets/qSlicerGUIWidgetsModule.cxx create mode 100644 GUIWidgets/qSlicerGUIWidgetsModule.h create mode 100644 GUIWidgets/qSlicerGUIWidgetsModuleWidget.cxx create mode 100644 GUIWidgets/qSlicerGUIWidgetsModuleWidget.h create mode 100644 PointerSimulator/CMakeLists.txt create mode 100644 PointerSimulator/PointerSimulator.py create mode 100644 PointerSimulator/Resources/Icons/PointerSimulator.png create mode 100644 PointerSimulator/Resources/UI/PointerSimulator.ui create mode 100644 PointerSimulator/Testing/CMakeLists.txt create mode 100644 PointerSimulator/Testing/Python/CMakeLists.txt create mode 100644 PointerSimulator/__pycache__/PointerSimulator.cpython-36.pyc create mode 100644 VirtualReality/Resources/menuTextureImage2.png create mode 100644 VirtualReality/Widgets/Resources/StyleSheets/VrWidgetStyle.qss create mode 100644 VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealityDataModuleWidget.ui create mode 100644 VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealityHomeWidget.ui create mode 100644 VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealitySegmentEditorWidget.ui create mode 100644 VirtualReality/Widgets/Resources/qSlicerVirtualRealityModuleWidgets.qrc create mode 100644 VirtualReality/Widgets/qMRMLVirtualRealityDataModuleWidget.cxx create mode 100644 VirtualReality/Widgets/qMRMLVirtualRealityDataModuleWidget.h create mode 100644 VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.cxx create mode 100644 VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.h create mode 100644 VirtualReality/Widgets/qMRMLVirtualRealitySegmentEditorWidget.cxx create mode 100644 VirtualReality/Widgets/qMRMLVirtualRealitySegmentEditorWidget.h create mode 100644 VirtualReality/qSlicerHomeVirtualWidget.cxx create mode 100644 VirtualReality/qSlicerHomeVirtualWidget.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 54d36ae..776152f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,8 @@ endif() #----------------------------------------------------------------------------- # Extension modules add_subdirectory(VirtualReality) +add_subdirectory(GUIWidgets) +add_subdirectory(PointerSimulator) ## NEXT_MODULE #----------------------------------------------------------------------------- diff --git a/GUIWidgets/CMakeLists.txt b/GUIWidgets/CMakeLists.txt new file mode 100644 index 0000000..c5dc5a0 --- /dev/null +++ b/GUIWidgets/CMakeLists.txt @@ -0,0 +1,81 @@ + +#----------------------------------------------------------------------------- +set(MODULE_NAME GUIWidgets) +set(MODULE_TITLE ${MODULE_NAME}) + +string(TOUPPER ${MODULE_NAME} MODULE_NAME_UPPER) + +#----------------------------------------------------------------------------- +add_subdirectory(MRML) +add_subdirectory(Logic) +add_subdirectory(VTKWidgets) +# add_subdirectory(MRMLDM) +# add_subdirectory(Widgets) + +#----------------------------------------------------------------------------- +set(MODULE_EXPORT_DIRECTIVE "Q_SLICER_QTMODULES_${MODULE_NAME_UPPER}_EXPORT") + +# Current_{source,binary} and Slicer_{Libs,Base} already included +set(MODULE_INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR}/MRML + ${CMAKE_CURRENT_BINARY_DIR}/MRML + ${CMAKE_CURRENT_SOURCE_DIR}/Logic + ${CMAKE_CURRENT_BINARY_DIR}/Logic + ${CMAKE_CURRENT_SOURCE_DIR}/VTKWidgets + ${CMAKE_CURRENT_BINARY_DIR}/VTKWidgets + # ${CMAKE_CURRENT_SOURCE_DIR}/Widgets + # ${CMAKE_CURRENT_BINARY_DIR}/Widgets + ${qSlicerMarkupsModuleWidgets_INCLUDE_DIRS} + ${qSlicerVirtualRealityModuleWidgets_INCLUDE_DIRS} + ) + +set(MODULE_SRCS + qSlicer${MODULE_NAME}Module.cxx + qSlicer${MODULE_NAME}Module.h + qSlicer${MODULE_NAME}ModuleWidget.cxx + qSlicer${MODULE_NAME}ModuleWidget.h + ) + +set(MODULE_MOC_SRCS + qSlicer${MODULE_NAME}Module.h + qSlicer${MODULE_NAME}ModuleWidget.h + ) + +set(MODULE_UI_SRCS + Resources/UI/qSlicer${MODULE_NAME}ModuleWidget.ui + ) + +set(MODULE_TARGET_LIBRARIES + vtkSlicer${MODULE_NAME}ModuleMRML + vtkSlicer${MODULE_NAME}ModuleLogic + vtkSlicer${MODULE_NAME}ModuleVTKWidgets + # vtkSlicer${MODULE_NAME}ModuleMRMLDisplayableManager + # qSlicer${MODULE_NAME}ModuleWidgets + vtkSlicerMarkupsModuleMRMLDisplayableManager + qSlicerMarkupsModuleWidgets + qSlicerVirtualRealityModuleWidgets + vtkSlicerVirtualRealityModuleLogic + ) + +set(MODULE_RESOURCES + Resources/qSlicer${MODULE_NAME}Module.qrc + ) + +#----------------------------------------------------------------------------- +slicerMacroBuildLoadableModule( + NAME ${MODULE_NAME} + TITLE ${MODULE_TITLE} + EXPORT_DIRECTIVE ${MODULE_EXPORT_DIRECTIVE} + INCLUDE_DIRECTORIES ${MODULE_INCLUDE_DIRECTORIES} + SRCS ${MODULE_SRCS} + MOC_SRCS ${MODULE_MOC_SRCS} + UI_SRCS ${MODULE_UI_SRCS} + TARGET_LIBRARIES ${MODULE_TARGET_LIBRARIES} + RESOURCES ${MODULE_RESOURCES} + WITH_GENERIC_TESTS + ) + +#----------------------------------------------------------------------------- +if(BUILD_TESTING) + add_subdirectory(Testing) +endif() diff --git a/GUIWidgets/Logic/CMakeLists.txt b/GUIWidgets/Logic/CMakeLists.txt new file mode 100644 index 0000000..4554569 --- /dev/null +++ b/GUIWidgets/Logic/CMakeLists.txt @@ -0,0 +1,27 @@ +project(vtkSlicer${MODULE_NAME}ModuleLogic) + +set(KIT ${PROJECT_NAME}) + +set(${KIT}_EXPORT_DIRECTIVE "VTK_SLICER_${MODULE_NAME_UPPER}_MODULE_LOGIC_EXPORT") + +set(${KIT}_INCLUDE_DIRECTORIES + ) + +set(${KIT}_SRCS + vtkSlicer${MODULE_NAME}Logic.cxx + vtkSlicer${MODULE_NAME}Logic.h + ) + +set(${KIT}_TARGET_LIBRARIES + vtkSlicer${MODULE_NAME}ModuleMRML + vtkSlicerMarkupsModuleLogic + ) + +#----------------------------------------------------------------------------- +SlicerMacroBuildModuleLogic( + NAME ${KIT} + EXPORT_DIRECTIVE ${${KIT}_EXPORT_DIRECTIVE} + INCLUDE_DIRECTORIES ${${KIT}_INCLUDE_DIRECTORIES} + SRCS ${${KIT}_SRCS} + TARGET_LIBRARIES ${${KIT}_TARGET_LIBRARIES} + ) diff --git a/GUIWidgets/Logic/vtkSlicerGUIWidgetsLogic.cxx b/GUIWidgets/Logic/vtkSlicerGUIWidgetsLogic.cxx new file mode 100644 index 0000000..15ebef8 --- /dev/null +++ b/GUIWidgets/Logic/vtkSlicerGUIWidgetsLogic.cxx @@ -0,0 +1,143 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==============================================================================*/ + +// GUIWidgets Logic includes +#include "vtkSlicerGUIWidgetsLogic.h" + +// GUIWidgets MRML includes +#include "vtkMRMLGUIWidgetNode.h" +#include "vtkMRMLGUIWidgetDisplayNode.h" + +// MRML includes +#include +#include + +// Markups logic includes +#include + +// VTK includes +#include +#include +#include + +// STD includes +#include + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(vtkSlicerGUIWidgetsLogic); + +//---------------------------------------------------------------------------- +vtkSlicerGUIWidgetsLogic::vtkSlicerGUIWidgetsLogic() +{ +} + +//---------------------------------------------------------------------------- +vtkSlicerGUIWidgetsLogic::~vtkSlicerGUIWidgetsLogic() +{ +} + +//---------------------------------------------------------------------------- +void vtkSlicerGUIWidgetsLogic::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +//--------------------------------------------------------------------------- +void vtkSlicerGUIWidgetsLogic::SetMRMLSceneInternal(vtkMRMLScene * newScene) +{ + vtkNew events; + events->InsertNextValue(vtkMRMLScene::NodeAddedEvent); + events->InsertNextValue(vtkMRMLScene::NodeRemovedEvent); + events->InsertNextValue(vtkMRMLScene::EndBatchProcessEvent); + this->SetAndObserveMRMLSceneEventsInternal(newScene, events.GetPointer()); +} + +//--------------------------------------------------------------------------- +void vtkSlicerGUIWidgetsLogic::ObserveMRMLScene() +{ + if (!this->GetMRMLScene()) + { + return; + } + + vtkMRMLApplicationLogic* mrmlAppLogic = this->GetMRMLApplicationLogic(); + if (!mrmlAppLogic) + { + vtkErrorMacro("ObserveMRMLScene: invalid MRML Application Logic"); + return; + } + + vtkMRMLNode* node = this->GetMRMLScene()->GetNodeByID(this->GetSelectionNodeID().c_str()); + if (!node) + { + vtkErrorMacro("Observe MRMLScene: invalid Selection Node"); + return; + } + + // add known markup types to the selection node + vtkMRMLSelectionNode* selectionNode = vtkMRMLSelectionNode::SafeDownCast(node); + if (selectionNode) + { + // got into batch process mode so that an update on the mouse mode tool + // bar is triggered when leave it + this->GetMRMLScene()->StartState(vtkMRMLScene::BatchProcessState); + + auto guiWidgetNode = vtkSmartPointer::New(); + selectionNode->AddNewPlaceNodeClassNameToList( + guiWidgetNode->GetClassName(), guiWidgetNode->GetAddIcon(), guiWidgetNode->GetMarkupType()); + + // trigger an update on the mouse mode toolbar + this->GetMRMLScene()->EndState(vtkMRMLScene::BatchProcessState); + } + + this->Superclass::ObserveMRMLScene(); +} + +//----------------------------------------------------------------------------- +void vtkSlicerGUIWidgetsLogic::RegisterNodes() +{ + vtkMRMLScene* scene = this->GetMRMLScene(); + if (!scene) + { + vtkErrorMacro("RegisterNodes: Invalid MRML scene"); + return; + } + if (!scene->IsNodeClassRegistered("vtkMRMLGUIWidgetNode")) + { + scene->RegisterNodeClass(vtkSmartPointer::New()); + } + if (!scene->IsNodeClassRegistered("vtkMRMLGUIWidgetDisplayNode")) + { + scene->RegisterNodeClass(vtkSmartPointer::New()); + } +} + +//--------------------------------------------------------------------------- +void vtkSlicerGUIWidgetsLogic::UpdateFromMRMLScene() +{ + assert(this->GetMRMLScene() != 0); +} + +//--------------------------------------------------------------------------- +void vtkSlicerGUIWidgetsLogic::OnMRMLSceneNodeAdded(vtkMRMLNode* vtkNotUsed(node)) +{ +} + +//--------------------------------------------------------------------------- +void vtkSlicerGUIWidgetsLogic::OnMRMLSceneNodeRemoved(vtkMRMLNode* vtkNotUsed(node)) +{ +} diff --git a/GUIWidgets/Logic/vtkSlicerGUIWidgetsLogic.h b/GUIWidgets/Logic/vtkSlicerGUIWidgetsLogic.h new file mode 100644 index 0000000..94be264 --- /dev/null +++ b/GUIWidgets/Logic/vtkSlicerGUIWidgetsLogic.h @@ -0,0 +1,63 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==============================================================================*/ + +// .NAME vtkSlicerGUIWidgetsLogic - slicer logic class for volumes manipulation +// .SECTION Description +// This class manages the logic associated with reading, saving, +// and changing propertied of the volumes + + +#ifndef __vtkSlicerGUIWidgetsLogic_h +#define __vtkSlicerGUIWidgetsLogic_h + +// Slicer includes +#include + +// MRML includes + +#include "vtkSlicerGUIWidgetsModuleLogicExport.h" + +/// \ingroup Slicer_QtModules_ExtensionTemplate +class VTK_SLICER_GUIWIDGETS_MODULE_LOGIC_EXPORT vtkSlicerGUIWidgetsLogic : + public vtkSlicerMarkupsLogic +{ +public: + + static vtkSlicerGUIWidgetsLogic *New(); + vtkTypeMacro(vtkSlicerGUIWidgetsLogic, vtkSlicerMarkupsLogic); + void PrintSelf(ostream& os, vtkIndent indent); + +protected: + vtkSlicerGUIWidgetsLogic(); + virtual ~vtkSlicerGUIWidgetsLogic(); + + /// Initialize listening to MRML events + virtual void SetMRMLSceneInternal(vtkMRMLScene* newScene); + void ObserveMRMLScene() override; + + /// Register MRML Node classes to Scene. Gets called automatically when the MRMLScene is attached to this logic class. + virtual void RegisterNodes(); + virtual void UpdateFromMRMLScene(); + virtual void OnMRMLSceneNodeAdded(vtkMRMLNode* node); + virtual void OnMRMLSceneNodeRemoved(vtkMRMLNode* node); +private: + + vtkSlicerGUIWidgetsLogic(const vtkSlicerGUIWidgetsLogic&); // Not implemented + void operator=(const vtkSlicerGUIWidgetsLogic&); // Not implemented +}; + +#endif diff --git a/GUIWidgets/MRML/CMakeLists.txt b/GUIWidgets/MRML/CMakeLists.txt new file mode 100644 index 0000000..a7b4fcc --- /dev/null +++ b/GUIWidgets/MRML/CMakeLists.txt @@ -0,0 +1,29 @@ +project(vtkSlicer${MODULE_NAME}ModuleMRML) + +set(KIT ${PROJECT_NAME}) + +set(${KIT}_EXPORT_DIRECTIVE "VTK_SLICER_${MODULE_NAME_UPPER}_MODULE_MRML_EXPORT") + +set(${KIT}_INCLUDE_DIRECTORIES + ${vtkSlicer${MODULE_NAME}ModuleVTKWidgets_SOURCE_DIR} + ${vtkSlicer${MODULE_NAME}ModuleVTKWidgets_BINARY_DIR} + ) + +set(${KIT}_SRCS + vtkMRMLGUIWidgetNode.cxx + vtkMRMLGUIWidgetDisplayNode.cxx + ) + +set(${KIT}_TARGET_LIBRARIES + ${MRML_LIBRARIES} + vtkSlicerMarkupsModuleMRML + ) + +#----------------------------------------------------------------------------- +SlicerMacroBuildModuleMRML( + NAME ${KIT} + EXPORT_DIRECTIVE ${${KIT}_EXPORT_DIRECTIVE} + INCLUDE_DIRECTORIES ${${KIT}_INCLUDE_DIRECTORIES} + SRCS ${${KIT}_SRCS} + TARGET_LIBRARIES ${${KIT}_TARGET_LIBRARIES} + ) diff --git a/GUIWidgets/MRML/vtkMRMLGUIWidgetDisplayNode.cxx b/GUIWidgets/MRML/vtkMRMLGUIWidgetDisplayNode.cxx new file mode 100644 index 0000000..2c6a73d --- /dev/null +++ b/GUIWidgets/MRML/vtkMRMLGUIWidgetDisplayNode.cxx @@ -0,0 +1,35 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +// MRML includes +#include "vtkMRMLGUIWidgetDisplayNode.h" + +//---------------------------------------------------------------------------- +vtkMRMLNodeNewMacro(vtkMRMLGUIWidgetDisplayNode); + +//---------------------------------------------------------------------------- +vtkMRMLGUIWidgetDisplayNode::vtkMRMLGUIWidgetDisplayNode() +{ +} + +//---------------------------------------------------------------------------- +vtkMRMLGUIWidgetDisplayNode::~vtkMRMLGUIWidgetDisplayNode() = default; diff --git a/GUIWidgets/MRML/vtkMRMLGUIWidgetDisplayNode.h b/GUIWidgets/MRML/vtkMRMLGUIWidgetDisplayNode.h new file mode 100644 index 0000000..9e09bf8 --- /dev/null +++ b/GUIWidgets/MRML/vtkMRMLGUIWidgetDisplayNode.h @@ -0,0 +1,59 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +// .NAME vtkMRMLGUIWidgetDisplayNode - MRML node to represent display properties for GUI widget +// .SECTION Description +// Adjusts default display parameters for ROI such as fill opacity. +// + +#ifndef __vtkMRMLGUIWidgetDisplayNode_h +#define __vtkMRMLGUIWidgetDisplayNode_h + +#include "vtkSlicerGUIWidgetsModuleMRMLExport.h" +#include "vtkMRMLMarkupsDisplayNode.h" + +class VTK_SLICER_GUIWIDGETS_MODULE_MRML_EXPORT vtkMRMLGUIWidgetDisplayNode : public vtkMRMLMarkupsDisplayNode +{ +public: + static vtkMRMLGUIWidgetDisplayNode *New(); + vtkTypeMacro(vtkMRMLGUIWidgetDisplayNode, vtkMRMLMarkupsDisplayNode); + + //-------------------------------------------------------------------------- + // MRMLNode methods + //-------------------------------------------------------------------------- + + vtkMRMLNode* CreateNodeInstance ( ) override; + + // Get node XML tag name (like Volume, Markups) + const char* GetNodeTagName() override {return "GUIWidgetDisplay";}; + + /// Copy node content (excludes basic data, such as name and node references). + /// \sa vtkMRMLNode::CopyContent + vtkMRMLCopyContentDefaultMacro(vtkMRMLGUIWidgetDisplayNode); + +protected: + vtkMRMLGUIWidgetDisplayNode(); + ~vtkMRMLGUIWidgetDisplayNode() override; + vtkMRMLGUIWidgetDisplayNode( const vtkMRMLGUIWidgetDisplayNode& ); + void operator= ( const vtkMRMLGUIWidgetDisplayNode& ); +}; +#endif diff --git a/GUIWidgets/MRML/vtkMRMLGUIWidgetNode.cxx b/GUIWidgets/MRML/vtkMRMLGUIWidgetNode.cxx new file mode 100644 index 0000000..e9b8c5a --- /dev/null +++ b/GUIWidgets/MRML/vtkMRMLGUIWidgetNode.cxx @@ -0,0 +1,140 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +// GUI Widgets includes +#include "vtkMRMLGUIWidgetNode.h" +#include "vtkMRMLGUIWidgetDisplayNode.h" + +// MRML includes +#include "vtkMRMLMeasurementArea.h" +#include "vtkMRMLScene.h" +#include "vtkMRMLStorageNode.h" +#include "vtkMRMLTransformNode.h" + +// VTK includes +#include +#include +#include +#include +#include +#include +#include + +// STD includes +#include + +//---------------------------------------------------------------------------- +vtkMRMLNodeNewMacro(vtkMRMLGUIWidgetNode); + +//---------------------------------------------------------------------------- +vtkMRMLGUIWidgetNode::vtkMRMLGUIWidgetNode() +{ +} + +//---------------------------------------------------------------------------- +vtkMRMLGUIWidgetNode::~vtkMRMLGUIWidgetNode() = default; + +//---------------------------------------------------------------------------- +void vtkMRMLGUIWidgetNode::WriteXML(ostream& of, int nIndent) +{ + Superclass::WriteXML(of,nIndent); + //vtkMRMLWriteXMLBeginMacro(of); + //vtkMRMLWriteXMLEnumMacro(sizeMode, SizeMode); + //vtkMRMLWriteXMLVectorMacro(size, Size, double, 2); + //vtkMRMLWriteXMLFloatMacro(autoSizeScalingFactor, AutoSizeScalingFactor); + //vtkMRMLWriteXMLMatrix4x4Macro(objectToBaseMatrix, ObjectToBaseMatrix); + //vtkMRMLWriteXMLEndMacro(); +} + +//---------------------------------------------------------------------------- +void vtkMRMLGUIWidgetNode::ReadXMLAttributes(const char** atts) +{ + MRMLNodeModifyBlocker blocker(this); + Superclass::ReadXMLAttributes(atts); + //vtkMRMLReadXMLBeginMacro(atts); + //vtkMRMLReadXMLEnumMacro(sizeMode, SizeMode); + //vtkMRMLReadXMLVectorMacro(size, Size, double, 2); + //vtkMRMLReadXMLFloatMacro(autoSizeScalingFactor, AutoSizeScalingFactor); + //vtkMRMLReadXMLOwnedMatrix4x4Macro(planeTobaseMatrix, ObjectToBaseMatrix); // Backwards compatible with old name + //vtkMRMLReadXMLOwnedMatrix4x4Macro(objectToBaseMatrix, ObjectToBaseMatrix); + //vtkMRMLReadXMLEndMacro(); +} + +//---------------------------------------------------------------------------- +void vtkMRMLGUIWidgetNode::CopyContent(vtkMRMLNode* anode, bool deepCopy/*=true*/) +{ + MRMLNodeModifyBlocker blocker(this); + Superclass::CopyContent(anode, deepCopy); + //vtkMRMLCopyBeginMacro(anode); + //vtkMRMLCopyEnumMacro(SizeMode); + //vtkMRMLCopyVectorMacro(Size, double, 2); + //vtkMRMLCopyFloatMacro(AutoSizeScalingFactor); + //vtkMRMLCopyOwnedMatrix4x4Macro(ObjectToBaseMatrix); + //vtkMRMLCopyEndMacro(); +} + +//---------------------------------------------------------------------------- +void vtkMRMLGUIWidgetNode::PrintSelf(ostream& os, vtkIndent indent) +{ + Superclass::PrintSelf(os,indent); + //vtkMRMLPrintBeginMacro(os, indent); + //vtkMRMLPrintEnumMacro(SizeMode); + //vtkMRMLPrintVectorMacro(Size, double, 2); + //vtkMRMLPrintFloatMacro(AutoSizeScalingFactor); + //vtkMRMLPrintMatrix4x4Macro(ObjectToBaseMatrix); + //vtkMRMLPrintEndMacro(); +} + +//---------------------------------------------------------------------------- +void vtkMRMLGUIWidgetNode::CreateDefaultDisplayNodes() +{ + if (this->GetDisplayNode() != nullptr && vtkMRMLGUIWidgetDisplayNode::SafeDownCast(this->GetDisplayNode()) != nullptr) + { + // Display node already exists + return; + } + if (this->GetScene() == nullptr) + { + vtkErrorMacro("vtkMRMLGUIWidgetNode::CreateDefaultDisplayNodes failed: scene is invalid"); + return; + } + vtkMRMLGUIWidgetDisplayNode* displayNode = vtkMRMLGUIWidgetDisplayNode::SafeDownCast( + this->GetScene()->AddNewNodeByClass("vtkMRMLGUIWidgetDisplayNode")); + if (!displayNode) + { + vtkErrorMacro("vtkMRMLGUIWidgetNode::CreateDefaultDisplayNodes failed: scene failed to instantiate a vtkMRMLGUIWidgetDisplayNode node"); + return; + } + this->SetAndObserveDisplayNodeID(displayNode->GetID()); +} + +//---------------------------------------------------------------------------- +void vtkMRMLGUIWidgetNode::SetWidget(void* w) +{ + if (this->Widget == w) + { + return; + } + + this->Widget = w; + this->Modified(); +} diff --git a/GUIWidgets/MRML/vtkMRMLGUIWidgetNode.h b/GUIWidgets/MRML/vtkMRMLGUIWidgetNode.h new file mode 100644 index 0000000..a13ec58 --- /dev/null +++ b/GUIWidgets/MRML/vtkMRMLGUIWidgetNode.h @@ -0,0 +1,85 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +#ifndef __vtkMRMLGUIWidgetNode_h +#define __vtkMRMLGUIWidgetNode_h + +#include "vtkSlicerGUIWidgetsModuleMRMLExport.h" + +// Markups includes +#include "vtkMRMLMarkupsPlaneNode.h" + +/// \brief MRML node to represent a planar GUI widget displayable in the 3D view +class VTK_SLICER_GUIWIDGETS_MODULE_MRML_EXPORT vtkMRMLGUIWidgetNode : public vtkMRMLMarkupsPlaneNode +{ +public: + static vtkMRMLGUIWidgetNode *New(); + vtkTypeMacro(vtkMRMLGUIWidgetNode, vtkMRMLMarkupsPlaneNode); + /// Print out the node information to the output stream + void PrintSelf(ostream& os, vtkIndent indent) override; + + //TODO: + //const char* GetIcon() override {return ":/Icons/MarkupsPlane.png";} + //const char* GetAddIcon() override {return ":/Icons/MarkupsPlaneMouseModePlace.png";} + //const char* GetPlaceAddIcon() override {return ":/Icons/MarkupsPlaneMouseModePlaceAdd.png";} + + vtkMRMLNode* CreateNodeInstance() override; + /// Get node XML tag name (like Volume, Model) + const char* GetNodeTagName() override {return "GUIWidget";} + + /// Get markup name + const char* GetMarkupType() override {return "GUIWidget";}; + + /// Get markup short name + const char* GetDefaultNodeNamePrefix() override {return "W";}; + + /// Read node attributes from XML file + void ReadXMLAttributes( const char** atts) override; + + /// Write this node's information to a MRML file in XML format. + void WriteXML(ostream& of, int indent) override; + + /// Copy node content (excludes basic data, such as name and node references). + /// \sa vtkMRMLNode::CopyContent + vtkMRMLCopyContentMacro(vtkMRMLGUIWidgetNode); + + /// Create default storage node or nullptr if does not have one + void CreateDefaultDisplayNodes() override; + +public: + /// Set the handle (must contain a QWidget) that will receive the events. + void SetWidget(void* w); + /// Get the QWidget handle + void* GetWidget() { return this->Widget; }; + +protected: + /// Handle for the widget to be rendered in the GUI widget markup + void* Widget{nullptr}; + +protected: + vtkMRMLGUIWidgetNode(); + ~vtkMRMLGUIWidgetNode() override; + vtkMRMLGUIWidgetNode(const vtkMRMLGUIWidgetNode&); + void operator=(const vtkMRMLGUIWidgetNode&); +}; + +#endif diff --git a/GUIWidgets/MRMLDM/CMakeLists.txt b/GUIWidgets/MRMLDM/CMakeLists.txt new file mode 100644 index 0000000..c476032 --- /dev/null +++ b/GUIWidgets/MRMLDM/CMakeLists.txt @@ -0,0 +1,47 @@ +project(vtkSlicer${MODULE_NAME}ModuleMRMLDisplayableManager) + +set(KIT ${PROJECT_NAME}) + +set(${KIT}_EXPORT_DIRECTIVE "VTK_SLICER_${MODULE_NAME_UPPER}_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT") + +set(${KIT}_INCLUDE_DIRECTORIES + ${vtkSlicer${MODULE_NAME}ModuleLogic_SOURCE_DIR} + ${vtkSlicer${MODULE_NAME}ModuleLogic_BINARY_DIR} + ${vtkSlicer${MODULE_NAME}ModuleMRML_SOURCE_DIR} + ${vtkSlicer${MODULE_NAME}ModuleMRML_BINARY_DIR} + ${vtkSlicer${MODULE_NAME}ModuleVTKWidgets_SOURCE_DIR} + ${vtkSlicer${MODULE_NAME}ModuleVTKWidgets_BINARY_DIR} +) + +set(DISPLAYABLE_MANAGER_SRCS + vtkMRML${MODULE_NAME}DisplayableManager.cxx + ) + +SlicerConfigureDisplayableManagerObjectFactory( + TARGET_NAME ${KIT} + SRCS "${DISPLAYABLE_MANAGER_SRCS}" + EXPORT_MACRO "${${KIT}_EXPORT_DIRECTIVE}" + EXPORT_HEADER "${KIT}Export.h" + OUTPUT_SRCS_VAR DISPLAYABLE_MANAGER_INSTANTIATOR_SRCS + ) + +set(${KIT}_SRCS + ${DISPLAYABLE_MANAGER_INSTANTIATOR_SRCS} + ${DISPLAYABLE_MANAGER_SRCS} + ) + +set(${KIT}_TARGET_LIBRARIES + ${MRML_LIBRARIES} + vtkSlicer${MODULE_NAME}ModuleLogic + vtkSlicer${MODULE_NAME}ModuleMRML + vtkSlicer${MODULE_NAME}ModuleVTKWidgets +) + +#----------------------------------------------------------------------------- +SlicerMacroBuildModuleLogic( + NAME ${KIT} + EXPORT_DIRECTIVE ${${KIT}_EXPORT_DIRECTIVE} + INCLUDE_DIRECTORIES ${${KIT}_INCLUDE_DIRECTORIES} + SRCS ${${KIT}_SRCS} + TARGET_LIBRARIES ${${KIT}_TARGET_LIBRARIES} +) diff --git a/GUIWidgets/MRMLDM/vtkMRMLGUIWidgetsDisplayableManager.cxx b/GUIWidgets/MRMLDM/vtkMRMLGUIWidgetsDisplayableManager.cxx new file mode 100644 index 0000000..7ed84f7 --- /dev/null +++ b/GUIWidgets/MRMLDM/vtkMRMLGUIWidgetsDisplayableManager.cxx @@ -0,0 +1,1319 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +#include "vtkMRMLGUIWidgetsDisplayableManager.h" + +#include "vtkMRMLAbstractSliceViewDisplayableManager.h" + +// GUI widgets includes +#include +#include + +// MarkupsModule/Logic includes +//#include + +// MRMLDisplayableManager includes +#include +#include + +// MRML includes +#include +#include +#include +#include +#include +//#include +//#include +//#include +#include +#include + +// VTK includes +#include +#include +//#include +#include +//#include +//#include +//#include +#include +#include +#include +//#include +//#include +//#include +#include +#include +//#include +//#include +//#include +//#include +//#include +//#include + +// STD includes +//#include +//#include +//#include +//#include +//#include + +//typedef void (*fp)(); + +//#define NUMERIC_ZERO 0.001 + +//--------------------------------------------------------------------------- +vtkStandardNewMacro(vtkMRMLGUIWidgetsDisplayableManager); + +//--------------------------------------------------------------------------- +class vtkMRMLGUIWidgetsDisplayableManager::vtkInternal +{ +public: + vtkInternal(vtkMRMLGUIWidgetsDisplayableManager* external); + ~vtkInternal(); + + //struct Pipeline + // { + + // Pipeline() + // { + // this->Actor = vtkSmartPointer::New(); + // vtkNew mapper; + // mapper->SetScalarVisibility(false); // ignore any scalars that an input mesh may contain + // this->Actor->SetMapper(mapper.GetPointer()); + // this->Actor->SetVisibility(false); + + // this->NodeToWorldTransform = vtkSmartPointer::New(); + // this->ModelWarper = vtkSmartPointer::New(); + // this->ModelWarper->SetTransform(this->NodeToWorldTransform); + // mapper->SetInputConnection(this->ModelWarper->GetOutputPort()); + // } + + // vtkSmartPointer Actor; + // vtkSmartPointer NodeToWorldTransform; + // vtkSmartPointer ModelWarper; + // }; + + //typedef std::map PipelineMapType; // first: segment ID; second: display pipeline + //typedef std::map PipelinesCacheType; + //PipelinesCacheType DisplayPipelines; + + //typedef std::map < vtkMRMLSegmentationNode*, std::set< vtkMRMLSegmentationDisplayNode* > > SegmentationToDisplayCacheType; + //SegmentationToDisplayCacheType SegmentationToDisplayNodes; + + /// Get widget for a given display node + vtkSlicerQWidgetWidget* GetWidget(vtkMRMLGUIWidgetDisplayNode* guiWidgetDisplayNode); + /// Get first visible widget for this markup + vtkSlicerQWidgetWidget* GetWidget(vtkMRMLGUIWidgetNode* guiWidgetNode); + + /// Remove all widgets, intersection widgets, nodes + void RemoveAllWidgetsAndNodes(); + + void AddGUIWidgetNode(vtkMRMLGUIWidgetNode* node); + void RemoveGUIWidgetNode(vtkMRMLGUIWidgetNode* node); + void AddDisplayNode(vtkMRMLGUIWidgetDisplayNode* displayNode); + void RemoveDisplayNode(vtkMRMLGUIWidgetDisplayNode* displayNode); + + void DeleteWidget(vtkSlicerQWidgetWidget* widget); + + void AddObservations(vtkMRMLGUIWidgetNode* node); + void RemoveObservations(vtkMRMLGUIWidgetNode* node); + +public: + /// Picker of segment prop in renderer + //vtkSmartPointer CellPicker; + + /// Last picked segmentation display node ID + //std::string PickedDisplayNodeID; + + /// Map of vtkWidget indexed using associated node ID + typedef std::map, vtkSlicerQWidgetWidget* > DisplayNodeToWidgetType; + typedef std::map, vtkSlicerQWidgetWidget* >::iterator DisplayNodeToWidgetIt; + DisplayNodeToWidgetType GUIWidgetDisplayNodesToWidgets; // display nodes with widgets assigned + + typedef std::set > GUIWidgetNodesType; + typedef std::set >::iterator GUIWidgetNodesIt; + GUIWidgetNodesType GUIWidgetNodes; // observed GUI widget nodes + +private: + vtkMRMLGUIWidgetsDisplayableManager* External; + + bool AddingGUIWidgetNode; + + std::vector ObservedGuiWidgetNodeEvents; +}; + +//--------------------------------------------------------------------------- +// vtkInternal methods + +//--------------------------------------------------------------------------- +vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::vtkInternal(vtkMRMLGUIWidgetsDisplayableManager* external) +: External(external) +, AddingGUIWidgetNode(false) +//, AddingSegmentationNode(false) +{ + //this->CellPicker = vtkSmartPointer::New(); + //this->CellPicker->SetTolerance(0.00001); +} + +//--------------------------------------------------------------------------- +vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::~vtkInternal() +{ +// this->ClearDisplayableNodes(); +} + +//--------------------------------------------------------------------------- +vtkSlicerQWidgetWidget* vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::GetWidget(vtkMRMLGUIWidgetNode* guiWidgetNode) +{ + if (!guiWidgetNode) + { + return nullptr; + } + vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::GUIWidgetNodesIt displayableIt = this->GUIWidgetNodes.find(guiWidgetNode); + if (displayableIt == this->GUIWidgetNodes.end()) + { + // we do not manage this markup + return nullptr; + } + + // Return first widget found for a GUI widget node + for (vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::DisplayNodeToWidgetIt widgetIterator = this->GUIWidgetDisplayNodesToWidgets.begin(); + widgetIterator != this->GUIWidgetDisplayNodesToWidgets.end(); ++widgetIterator) + { + vtkMRMLGUIWidgetDisplayNode* guiWidgetDisplayNode = widgetIterator->first; + if (guiWidgetDisplayNode->GetDisplayableNode() == guiWidgetNode) + { + return widgetIterator->second; + } + } + + return nullptr; +} + +//--------------------------------------------------------------------------- +vtkSlicerQWidgetWidget* vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::GetWidget(vtkMRMLGUIWidgetDisplayNode* node) +{ + if (!node) + { + return nullptr; + } + + // Make sure the map contains a vtkWidget associated with this node + DisplayNodeToWidgetIt it = this->GUIWidgetDisplayNodesToWidgets.find(node); + if (it == this->GUIWidgetDisplayNodesToWidgets.end()) + { + return nullptr; + } + + return it->second; +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::AddGUIWidgetNode(vtkMRMLGUIWidgetNode* node) +{ + if (!node) + { + return; + } + vtkMRMLAbstractViewNode* viewNode = vtkMRMLAbstractViewNode::SafeDownCast(this->External->GetMRMLDisplayableNode()); + if (!viewNode) + { + return; + } + + if (this->AddingGUIWidgetNode) + { + return; + } + this->AddingGUIWidgetNode = true; + + this->AddObservations(node); + this->GUIWidgetNodes.insert(node); + + // Add Display Nodes + int nnodes = node->GetNumberOfDisplayNodes(); + for (int i = 0; iGetNthDisplayNode(i)); + + // Check whether DisplayNode should be shown in this view + if (!displayNode + || !displayNode->IsA("vtkMRMLGUIWidgetDisplayNode") + || !displayNode->IsDisplayableInView(viewNode->GetID())) + { + continue; + } + + this->AddDisplayNode(displayNode); + } + + this->AddingGUIWidgetNode = false; +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::RemoveGUIWidgetNode(vtkMRMLGUIWidgetNode* node) +{ + if (!node) + { + return; + } + + vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::GUIWidgetNodesIt displayableIt = + this->GUIWidgetNodes.find(node); + + if (displayableIt == this->GUIWidgetNodes.end()) + { + // we do not manage this markup + return; + } + + // Remove display nodes corresponding to this GUI widget node + for (vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::DisplayNodeToWidgetIt widgetIterator = this->GUIWidgetDisplayNodesToWidgets.begin(); + widgetIterator != this->GUIWidgetDisplayNodesToWidgets.end(); + /*upon deletion the increment is done already, so don't increment here*/) + { + vtkMRMLGUIWidgetDisplayNode* guiWidgetDisplayNode = widgetIterator->first; + if (guiWidgetDisplayNode->GetDisplayableNode() != node) + { + ++widgetIterator; + } + else + { + // display node of the node that is being removed + vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::DisplayNodeToWidgetIt widgetIteratorToRemove = widgetIterator; + ++widgetIterator; + vtkSlicerQWidgetWidget* widgetToRemove = widgetIteratorToRemove->second; + this->DeleteWidget(widgetToRemove); + this->GUIWidgetDisplayNodesToWidgets.erase(widgetIteratorToRemove); + } + } + + this->RemoveObservations(node); + this->GUIWidgetNodes.erase(displayableIt); +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::AddDisplayNode(vtkMRMLGUIWidgetDisplayNode* guiWidgetDisplayNode) +{ + if (!guiWidgetDisplayNode) + { + return; + } + + // Do not add the display node if displayNodeIt is already associated with a widget object. + // This happens when a segmentation node already associated with a display node + // is copied into an other (using vtkMRMLNode::Copy()) and is added to the scene afterward. + // Related issue are #3428 and #2608 + vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::DisplayNodeToWidgetIt displayNodeIt + = this->GUIWidgetDisplayNodesToWidgets.find(guiWidgetDisplayNode); + if (displayNodeIt != this->GUIWidgetDisplayNodesToWidgets.end()) + { + return; + } + + // There should not be a widget for the new node + if (this->GetWidget(guiWidgetDisplayNode) != nullptr) + { + vtkErrorWithObjectMacro(this->External, "vtkMRMLGUIWidgetsDisplayableManager: A widget is already associated to this node"); + return; + } + + vtkSlicerQWidgetWidget* newWidget = this->External->CreateWidget(guiWidgetDisplayNode); + if (!newWidget) + { + vtkErrorWithObjectMacro(this->External, "vtkMRMLGUIWidgetsDisplayableManager: Failed to create widget"); + return; + } + + // record the mapping between node and widget in the helper + this->GUIWidgetDisplayNodesToWidgets[guiWidgetDisplayNode] = newWidget; + + // Build representation + newWidget->UpdateFromMRML(guiWidgetDisplayNode, 0); // no specific event triggers full rebuild + + this->External->RequestRender(); + + // Update cached matrices. Calls UpdateWidget + //this->UpdateDisplayableTransforms(mNode); +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::RemoveDisplayNode(vtkMRMLGUIWidgetDisplayNode* guiWidgetDisplayNode) +{ + if (!guiWidgetDisplayNode) + { + return; + } + + vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::DisplayNodeToWidgetIt displayNodeIt + = this->GUIWidgetDisplayNodesToWidgets.find(guiWidgetDisplayNode); + if (displayNodeIt == this->GUIWidgetDisplayNodesToWidgets.end()) + { + // no widget found for this display node + return; + } + + vtkSlicerQWidgetWidget* widget = (displayNodeIt->second); + this->DeleteWidget(widget); + + this->GUIWidgetDisplayNodesToWidgets.erase(guiWidgetDisplayNode); +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::RemoveAllWidgetsAndNodes() +{ + DisplayNodeToWidgetIt guiWidgetIterator = this->GUIWidgetDisplayNodesToWidgets.begin(); + for (guiWidgetIterator = this->GUIWidgetDisplayNodesToWidgets.begin(); + guiWidgetIterator != this->GUIWidgetDisplayNodesToWidgets.end(); ++guiWidgetIterator) + { + guiWidgetIterator->second->Delete(); + } + this->GUIWidgetDisplayNodesToWidgets.clear(); + + GUIWidgetNodesIt guiWidgetNodeIterator = this->GUIWidgetNodes.begin(); + for (guiWidgetNodeIterator = this->GUIWidgetNodes.begin(); + guiWidgetNodeIterator != this->GUIWidgetNodes.end(); ++guiWidgetNodeIterator) + { + this->RemoveObservations(*guiWidgetNodeIterator); + } +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::DeleteWidget(vtkSlicerQWidgetWidget* widget) +{ + if (!widget) + { + return; + } + widget->SetRenderer(nullptr); + widget->SetRepresentation(nullptr); + widget->Delete(); +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::AddObservations(vtkMRMLGUIWidgetNode* node) +{ + vtkCallbackCommand* callbackCommand = this->External->GetMRMLNodesCallbackCommand(); + vtkEventBroker* broker = vtkEventBroker::GetInstance(); + for (auto observedMarkupNodeEvent : this->ObservedGuiWidgetNodeEvents) + { + if (!broker->GetObservationExist(node, observedMarkupNodeEvent, this->External, callbackCommand)) + { + broker->AddObservation(node, observedMarkupNodeEvent, this->External, callbackCommand); + } + } +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::RemoveObservations(vtkMRMLGUIWidgetNode* node) +{ + vtkCallbackCommand* callbackCommand = this->External->GetMRMLNodesCallbackCommand(); + vtkEventBroker* broker = vtkEventBroker::GetInstance(); + for (auto observedMarkupNodeEvent : this->ObservedGuiWidgetNodeEvents) + { + vtkEventBroker::ObservationVector observations; + observations = broker->GetObservations(node, observedMarkupNodeEvent, this->External, callbackCommand); + broker->RemoveObservations(observations); + } +} + + +//--------------------------------------------------------------------------- +// vtkMRMLGUIWidgetsDisplayableManager methods + +//--------------------------------------------------------------------------- +vtkMRMLGUIWidgetsDisplayableManager::vtkMRMLGUIWidgetsDisplayableManager() +{ + this->Internal = new vtkInternal(this); + + /* + this->Internal = vtkSmartPointer::New(); + this->Internal->SetDisplayableManager(this); + this->DisableInteractorStyleEventsProcessing = 0; + + this->LastClickWorldCoordinates[0]=0.0; + this->LastClickWorldCoordinates[1]=0.0; + this->LastClickWorldCoordinates[2]=0.0; + this->LastClickWorldCoordinates[3]=1.0; + */ +} + +//--------------------------------------------------------------------------- +vtkMRMLGUIWidgetsDisplayableManager::~vtkMRMLGUIWidgetsDisplayableManager() +{ + //this->DisableInteractorStyleEventsProcessing = 0; + + delete this->Internal; + this->Internal = nullptr; +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + /* + os << indent << "DisableInteractorStyleEventsProcessing = " << this->DisableInteractorStyleEventsProcessing << std::endl; + if (this->SliceNode && + this->SliceNode->GetID()) + { + os << indent << "Slice node id = " << this->SliceNode->GetID() << std::endl; + } + else + { + os << indent << "No slice node" << std::endl; + } + */ +} +/* +//--------------------------------------------------------------------------- +vtkMRMLSliceNode * vtkMRMLGUIWidgetsDisplayableManager::GetMRMLSliceNode() +{ + return vtkMRMLSliceNode::SafeDownCast(this->GetMRMLDisplayableNode()); +} + +//--------------------------------------------------------------------------- +bool vtkMRMLGUIWidgetsDisplayableManager::Is2DDisplayableManager() +{ + return this->GetMRMLSliceNode() != nullptr; +} +*/ +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::RequestRender() +{ + if (!this->GetMRMLScene()) + { + return; + } + if (!this->GetMRMLScene()->IsBatchProcessing()) + { + this->Superclass::RequestRender(); + } +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::UpdateFromMRML() +{ + // this gets called from RequestRender, so make sure to jump out quickly if possible + if (this->GetMRMLScene() == nullptr) + { + return; + } + + // turn off update from mrml requested, as we're doing it now, and create + // widget requests a render which checks this flag before calling update + // from mrml again + this->SetUpdateFromMRMLRequested(false); + + std::vector guiWidgetNodes; + this->GetMRMLScene()->GetNodesByClass("vtkMRMLGUIWidgetNode", guiWidgetNodes); + for (std::vector< vtkMRMLNode* >::iterator nodeIt = guiWidgetNodes.begin(); nodeIt != guiWidgetNodes.end(); ++nodeIt) + { + vtkMRMLGUIWidgetNode* guiWidgetNode = vtkMRMLGUIWidgetNode::SafeDownCast(*nodeIt); + if (!guiWidgetNode) + { + continue; + } + if (this->Internal->GUIWidgetNodes.find(guiWidgetNode) != this->Internal->GUIWidgetNodes.end()) + { + // node added already + continue; + } + this->Internal->AddGUIWidgetNode(guiWidgetNode); + } + + std::vector guiWidgetDisplayNodes; + this->GetMRMLScene()->GetNodesByClass("vtkMRMLGUIWidgetDisplayNode", guiWidgetDisplayNodes); + for (std::vector< vtkMRMLNode* >::iterator nodeIt = guiWidgetDisplayNodes.begin(); + nodeIt != guiWidgetDisplayNodes.end(); ++nodeIt) + { + vtkMRMLGUIWidgetDisplayNode* guiWidgetDisplayNode = vtkMRMLGUIWidgetDisplayNode::SafeDownCast(*nodeIt); + if (!guiWidgetDisplayNode) + { + continue; + } + if (this->Internal->GUIWidgetDisplayNodesToWidgets.find(guiWidgetDisplayNode) != this->Internal->GUIWidgetDisplayNodesToWidgets.end()) + { + // node added already + continue; + } + this->Internal->AddDisplayNode(guiWidgetDisplayNode); + } + + // Remove observed GUI widget nodes that have been deleted from the scene + for (vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::GUIWidgetNodesIt guiWidgetIterator = + this->Internal->GUIWidgetNodes.begin(); guiWidgetIterator != this->Internal->GUIWidgetNodes.end(); ) + { + vtkMRMLGUIWidgetNode* guiWidgetNode = *guiWidgetIterator; + if (this->GetMRMLScene()->IsNodePresent(guiWidgetNode)) + { + ++guiWidgetIterator; + } + else + { + // display node is not in the scene anymore, delete the widget + vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::GUIWidgetNodesIt guiWidgetIteratorToRemove = guiWidgetIterator; + ++guiWidgetIterator; + this->Internal->RemoveGUIWidgetNode(*guiWidgetIteratorToRemove); + this->Internal->GUIWidgetNodes.erase(guiWidgetIteratorToRemove); + } + } + + // Remove widgets corresponding deleted display nodes + for (vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::DisplayNodeToWidgetIt widgetIterator = this->Internal->GUIWidgetDisplayNodesToWidgets.begin(); + widgetIterator != this->Internal->GUIWidgetDisplayNodesToWidgets.end(); ) + { + vtkMRMLGUIWidgetDisplayNode* guiWidgetDisplayNode = widgetIterator->first; + if (this->GetMRMLScene()->IsNodePresent(guiWidgetDisplayNode)) + { + ++widgetIterator; + } + else + { + // display node is not in the scene anymore, delete the widget + vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::DisplayNodeToWidgetIt widgetIteratorToRemove = widgetIterator; + ++widgetIterator; + vtkSlicerQWidgetWidget* widgetToRemove = widgetIteratorToRemove->second; + this->Internal->DeleteWidget(widgetToRemove); + this->Internal->GUIWidgetDisplayNodesToWidgets.erase(widgetIteratorToRemove); + } + } +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::SetMRMLSceneInternal(vtkMRMLScene* newScene) +{ + Superclass::SetMRMLSceneInternal(newScene); + + // after a new scene got associated, we want to make sure everything old is gone + this->OnMRMLSceneEndClose(); + + //if (newScene) + //{ + // this->AddObserversToInteractionNode(); + //} + //else + //{ + // // there's no scene to get the interaction node from, so this won't do anything + // this->RemoveObserversFromInteractionNode(); + //} + //vtkDebugMacro("SetMRMLSceneInternal: add observer on interaction node now?"); + + // clear out the map of glyph types + //this->Internal->ClearNodeGlyphTypes(); +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager +::ProcessMRMLNodesEvents(vtkObject *caller, unsigned long event, void *callData) +{ + vtkMRMLGUIWidgetNode* guiWidgetNode = vtkMRMLGUIWidgetNode::SafeDownCast(caller); + vtkMRMLInteractionNode* interactionNode = vtkMRMLInteractionNode::SafeDownCast(caller); + if (guiWidgetNode) + { + bool renderRequested = false; + + for (int displayNodeIndex = 0; displayNodeIndex < guiWidgetNode->GetNumberOfDisplayNodes(); displayNodeIndex++) + { + vtkMRMLGUIWidgetDisplayNode* displayNode = vtkMRMLGUIWidgetDisplayNode::SafeDownCast(guiWidgetNode->GetNthDisplayNode(displayNodeIndex)); + vtkSlicerQWidgetWidget* widget = this->Internal->GetWidget(displayNode); + if (!widget) + { + // if a new display node is added or display node view node IDs are changed then we may need to create a new widget + this->Internal->AddDisplayNode(displayNode); + widget = this->Internal->GetWidget(displayNode); + } + if (!widget) + { + continue; + } + widget->UpdateFromMRML(guiWidgetNode, event, callData); + if (widget->GetNeedToRender()) + { + renderRequested = true; + widget->NeedToRenderOff(); + } + } + + if (renderRequested) + { + this->RequestRender(); + } + } + else if (interactionNode) + { + if (event == vtkMRMLInteractionNode::InteractionModeChangedEvent) + { + // loop through all widgets and update the widget status + for (vtkMRMLGUIWidgetsDisplayableManager::vtkInternal::DisplayNodeToWidgetIt widgetIterator = this->Internal->GUIWidgetDisplayNodesToWidgets.begin(); + widgetIterator != this->Internal->GUIWidgetDisplayNodesToWidgets.end(); ++widgetIterator) + { + vtkSlicerQWidgetWidget* widget = widgetIterator->second; + if (!widget) + { + continue; + } + vtkMRMLInteractionEventData* eventData = reinterpret_cast(callData); + widget->Leave(eventData); + } + } + } + else + { + this->Superclass::ProcessMRMLNodesEvents(caller, event, callData); + } +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::OnMRMLSceneEndClose() +{ + //vtkDebugMacro("OnMRMLSceneEndClose: remove observers?"); + // run through all nodes and remove node and widget + this->Internal->RemoveAllWidgetsAndNodes(); + + this->SetUpdateFromMRMLRequested(true); + this->RequestRender(); +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::OnMRMLSceneEndImport() +{ + this->SetUpdateFromMRMLRequested(true); + this->UpdateFromMRMLScene(); + //this->Internal->SetAllWidgetsToManipulate(); + this->RequestRender(); +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::UpdateFromMRMLScene() +{ + if (this->GetMRMLDisplayableNode()) + { + this->UpdateFromMRML(); + } +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::OnMRMLSceneNodeAdded(vtkMRMLNode* node) +{ + if (!node || !this->GetMRMLScene()) + { + return; + } + + // if the scene is still updating, jump out + if (this->GetMRMLScene()->IsBatchProcessing()) + { + this->SetUpdateFromMRMLRequested(true); + return; + } + + if (node->IsA("vtkMRMLInteractionNode")) + { + //this->AddObserversToInteractionNode(); + return; + } + + if (node->IsA("vtkMRMLGUIWidgetNode")) + { + this->Internal->AddGUIWidgetNode(vtkMRMLGUIWidgetNode::SafeDownCast(node)); + + // and render again + this->RequestRender(); + } +} +/* +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::AddObserversToInteractionNode() +{ + if (!this->GetMRMLScene()) + { + return; + } + // also observe the interaction node for changes + vtkMRMLInteractionNode *interactionNode = this->GetInteractionNode(); + if (interactionNode) + { + vtkDebugMacro("AddObserversToInteractionNode: interactionNode found"); + vtkNew interactionEvents; + interactionEvents->InsertNextValue(vtkMRMLInteractionNode::InteractionModeChangedEvent); + interactionEvents->InsertNextValue(vtkMRMLInteractionNode::InteractionModePersistenceChangedEvent); + interactionEvents->InsertNextValue(vtkMRMLInteractionNode::EndPlacementEvent); + vtkObserveMRMLNodeEventsMacro(interactionNode, interactionEvents.GetPointer()); + } + else { vtkDebugMacro("AddObserversToInteractionNode: No interaction node!"); } +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::RemoveObserversFromInteractionNode() +{ + if (!this->GetMRMLScene()) + { + return; + } + + // find the interaction node + vtkMRMLInteractionNode *interactionNode = this->GetInteractionNode(); + if (interactionNode) + { + vtkUnObserveMRMLNodeMacro(interactionNode); + } +} +*/ +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::OnMRMLSceneNodeRemoved(vtkMRMLNode* node) +{ + bool modified = false; + + vtkMRMLGUIWidgetNode* guiWidgetNode = vtkMRMLGUIWidgetNode::SafeDownCast(node); + if (guiWidgetNode) + { + this->Internal->RemoveGUIWidgetNode(guiWidgetNode); + modified = true; + } + + vtkMRMLGUIWidgetDisplayNode* guiWidgetDisplayNode = vtkMRMLGUIWidgetDisplayNode::SafeDownCast(node); + if (guiWidgetDisplayNode) + { + this->Internal->RemoveDisplayNode(guiWidgetDisplayNode); + modified = true; + } + + if (modified) + { + this->RequestRender(); + } +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::OnMRMLDisplayableNodeModifiedEvent(vtkObject* caller) +{ + vtkDebugMacro("OnMRMLDisplayableNodeModifiedEvent"); + + if (!caller) + { + vtkErrorMacro("OnMRMLDisplayableNodeModifiedEvent: Could not get caller."); + return; + } + + //vtkMRMLSliceNode* sliceNode = vtkMRMLSliceNode::SafeDownCast(caller); + //if (sliceNode) + //{ + // // the associated renderWindow is a 2D SliceView + // // this is the entry point for all events fired by one of the three sliceviews + // // (e.g. change slice number, zoom etc.) + + // // we remember that this instance of the displayableManager deals with 2D + // // this is important for widget creation etc. and save the actual SliceNode + // // because during Slicer startup the SliceViews fire events, it will be always set correctly + // this->SliceNode = sliceNode; + + // // now we call the handle for specific sliceNode actions + // this->OnMRMLSliceNodeModifiedEvent(); + + // // and exit + // return; + //} + + //TODO: This is a no-op. Needed? + vtkMRMLViewNode* viewNode = vtkMRMLViewNode::SafeDownCast(caller); + if (viewNode) + { + // the associated renderWindow is a 3D View + vtkDebugMacro("OnMRMLDisplayableNodeModifiedEvent: This displayableManager handles a ThreeD view."); + return; + } +} +/* +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::OnMRMLSliceNodeModifiedEvent() +{ + bool renderRequested = false; + + // run through all markup nodes in the helper + vtkMRMLGUIWidgetsDisplayableManagerHelper::DisplayNodeToWidgetIt it + = this->Internal->MarkupsDisplayNodesToWidgets.begin(); + while(it != this->Internal->MarkupsDisplayNodesToWidgets.end()) + { + // we loop through all widgets + vtkSlicerMarkupsWidget* widget = (it->second); + widget->UpdateFromMRML(this->SliceNode, vtkCommand::ModifiedEvent); + if (widget->GetNeedToRender()) + { + renderRequested = true; + widget->NeedToRenderOff(); + } + ++it; + } + + if (renderRequested) + { + this->RequestRender(); + } +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::OnInteractorStyleEvent(int eventid) +{ + Superclass::OnInteractorStyleEvent(eventid); + +} + +//--------------------------------------------------------------------------- +vtkSlicerMarkupsWidget* vtkMRMLGUIWidgetsDisplayableManager::GetWidget(vtkMRMLMarkupsDisplayNode * node) +{ + return this->Internal->GetWidget(node); +} + +//--------------------------------------------------------------------------- +/// Check if it is the correct displayableManager +//--------------------------------------------------------------------------- +bool vtkMRMLGUIWidgetsDisplayableManager::IsCorrectDisplayableManager() +{ + vtkMRMLSelectionNode *selectionNode = this->GetMRMLApplicationLogic()->GetSelectionNode(); + if (selectionNode == nullptr) + { + vtkErrorMacro ("IsCorrectDisplayableManager: No selection node in the scene."); + return false; + } + const char* placeNodeClassName = selectionNode->GetActivePlaceNodeClassName(); + if (!placeNodeClassName) + { + return false; + } + + vtkSmartPointer node = + vtkSmartPointer::Take(this->GetMRMLScene()->CreateNodeByClass(placeNodeClassName)); + vtkMRMLMarkupsNode* markupsNode = vtkMRMLMarkupsNode::SafeDownCast(node); + if (!markupsNode) + { + return false; + } + + // the purpose of the displayableManager is hardcoded + return this->IsManageable(placeNodeClassName); + +} +//--------------------------------------------------------------------------- +bool vtkMRMLGUIWidgetsDisplayableManager::IsManageable(vtkMRMLNode* node) +{ + if (node == nullptr) + { + vtkErrorMacro("Is Manageable: invalid node."); + return false; + } + + return this->IsManageable(node->GetClassName()); +} + +//--------------------------------------------------------------------------- +bool vtkMRMLGUIWidgetsDisplayableManager::IsManageable(const char* nodeClassName) +{ + if (nodeClassName == nullptr) + { + return false; + } + + vtkSlicerMarkupsLogic* markupsLogic = + vtkSlicerMarkupsLogic::SafeDownCast(this->GetMRMLApplicationLogic()->GetModuleLogic("Markups")); + if (!markupsLogic) + { + vtkErrorMacro("Is Manageable: invalid Markups logic."); + return false; + } + + vtkSmartPointer node = + vtkSmartPointer::Take(this->GetMRMLScene()->CreateNodeByClass(nodeClassName)); + vtkMRMLMarkupsNode* markupsNode = vtkMRMLMarkupsNode::SafeDownCast(node); + if (!markupsNode) + { + return false; + } + + return markupsLogic->GetWidgetByMarkupsType(markupsNode->GetMarkupType()) ? true : false; +} + +//--------------------------------------------------------------------------- +vtkMRMLMarkupsNode* vtkMRMLGUIWidgetsDisplayableManager::CreateNewMarkupsNode( + const std::string &markupsNodeClassName) +{ + vtkMRMLMarkupsNode* markupsNode = vtkMRMLMarkupsNode::SafeDownCast( + this->GetMRMLScene()->AddNewNodeByClass(markupsNodeClassName)); + + std::string nodeName = + this->GetMRMLScene()->GenerateUniqueName(markupsNode->GetDefaultNodeNamePrefix()); + markupsNode->SetName(nodeName.c_str()); + markupsNode->AddDefaultStorageNode(); + markupsNode->CreateDefaultDisplayNodes(); + + return markupsNode; +} + +//--------------------------------------------------------------------------- +vtkSlicerMarkupsWidget* vtkMRMLGUIWidgetsDisplayableManager::FindClosestWidget(vtkMRMLInteractionEventData *callData, double &closestDistance2) +{ + vtkSlicerMarkupsWidget* closestWidget = nullptr; + closestDistance2 = VTK_DOUBLE_MAX; + + for (vtkMRMLGUIWidgetsDisplayableManagerHelper::DisplayNodeToWidgetIt widgetIterator = this->Internal->MarkupsDisplayNodesToWidgets.begin(); + widgetIterator != this->Internal->MarkupsDisplayNodesToWidgets.end(); ++widgetIterator) + { + vtkSlicerMarkupsWidget* widget = widgetIterator->second; + if (!widget) + { + continue; + } + double distance2FromWidget = VTK_DOUBLE_MAX; + if (widget->CanProcessInteractionEvent(callData, distance2FromWidget)) + { + if (!closestWidget || distance2FromWidget < closestDistance2) + { + closestDistance2 = distance2FromWidget; + closestWidget = widget; + } + } + } + return closestWidget; +} + +//--------------------------------------------------------------------------- +bool vtkMRMLGUIWidgetsDisplayableManager::CanProcessInteractionEvent(vtkMRMLInteractionEventData* eventData, double &closestDistance2) +{ + vtkMRMLInteractionNode* interactionNode = this->GetInteractionNode(); + // New point can be placed anywhere + int eventid = eventData->GetType(); + // We allow mouse move with the shift modifier to be processed while in place mode so that we can continue to update the + // preview positionm, even when using shift + mouse-move to adjust the crosshair position. + if ((eventid == vtkCommand::MouseMoveEvent + && (eventData->GetModifiers() == vtkEvent::NoModifier || + (eventData->GetModifiers() & vtkEvent::ShiftModifier && + interactionNode && interactionNode->GetCurrentInteractionMode() == vtkMRMLInteractionNode::Place))) + || eventid == vtkCommand::Move3DEvent + //|| (eventid == vtkCommand::LeftButtonPressEvent && eventData->GetModifiers() == vtkEvent::NoModifier) + //|| eventid == vtkCommand::LeftButtonReleaseEvent + //|| eventid == vtkCommand::RightButtonReleaseEvent + //|| eventid == vtkCommand::EnterEvent + //|| eventid == vtkCommand::LeaveEvent + ) + { + vtkMRMLSelectionNode *selectionNode = this->GetSelectionNode(); + if (!interactionNode || !selectionNode) + { + return false; + } + if (interactionNode->GetCurrentInteractionMode() == vtkMRMLInteractionNode::Place + && this->IsManageable(selectionNode->GetActivePlaceNodeClassName())) + { + + // If there is a suitable markups node for placement but it is not available in current view + // then we do not allow placement (placement would create a new markup node for this view, + // which would probably not what users want - they would like to place using the current markups node) + bool canPlaceInThisView = false; + vtkMRMLMarkupsNode* markupsNode = this->GetActiveMarkupsNodeForPlacement(); + if (markupsNode) + { + int numberOfDisplayNodes = markupsNode->GetNumberOfDisplayNodes(); + vtkMRMLAbstractViewNode* viewNode = vtkMRMLAbstractViewNode::SafeDownCast(this->GetMRMLDisplayableNode()); + for (int displayNodeIndex = 0; displayNodeIndex < numberOfDisplayNodes; displayNodeIndex++) + { + vtkMRMLDisplayNode* displayNode = markupsNode->GetNthDisplayNode(displayNodeIndex); + if (displayNode && displayNode->IsDisplayableInView(viewNode->GetID())) + { + canPlaceInThisView = true; + break; + } + } + } + else + { + // a new markups node will be created + canPlaceInThisView = true; + } + if (canPlaceInThisView) + { + closestDistance2 = 0.0; + return true; + } + } + } + + if (eventid == vtkCommand::LeaveEvent && this->LastActiveWidget != nullptr) + { + if (this->LastActiveWidget->GetMarkupsDisplayNode() && this->LastActiveWidget->GetMarkupsDisplayNode()->HasActiveComponent()) + { + // this widget has active component, therefore leave event is relevant + closestDistance2 = 0.0; + return this->LastActiveWidget; + } + } + + // Other interactions + bool canProcess = (this->FindClosestWidget(eventData, closestDistance2) != nullptr); + + if (!canProcess && this->LastActiveWidget != nullptr + && (eventid == vtkCommand::MouseMoveEvent || eventid == vtkCommand::Move3DEvent) ) + { + // interaction context (e.g. mouse) is moved away from the widget -> deactivate if it's the same context that activated it + std::vector contextsWithActiveComponents = + this->LastActiveWidget->GetMarkupsDisplayNode()->GetActiveComponentInteractionContexts(); + if (std::find(contextsWithActiveComponents.begin(), contextsWithActiveComponents.end(), eventData->GetInteractionContextName()) + != contextsWithActiveComponents.end() ) + { + this->LastActiveWidget->Leave(eventData); + this->LastActiveWidget = nullptr; + } + } + + return canProcess; +} + +//--------------------------------------------------------------------------- +bool vtkMRMLGUIWidgetsDisplayableManager::ProcessInteractionEvent(vtkMRMLInteractionEventData* eventData) +{ + if (this->GetDisableInteractorStyleEventsProcessing()) + { + return false; + } + int eventid = eventData->GetType(); + + if (eventid == vtkCommand::LeaveEvent) + { + if (this->LastActiveWidget != nullptr) + { + this->LastActiveWidget->Leave(eventData); + this->LastActiveWidget = nullptr; + } + } + + // Find/create active widget + vtkSlicerMarkupsWidget* activeWidget = nullptr; + if (this->GetInteractionNode()->GetCurrentInteractionMode() == vtkMRMLInteractionNode::Place) + { + activeWidget = this->GetWidgetForPlacement(); + if (activeWidget) + { + activeWidget->SetWidgetState(vtkSlicerMarkupsWidget::WidgetStateDefine); + } + } + else + { + double closestDistance2 = VTK_DOUBLE_MAX; + activeWidget = this->FindClosestWidget(eventData, closestDistance2); + } + + // Deactivate previous widget + if (this->LastActiveWidget != nullptr && this->LastActiveWidget != activeWidget) + { + this->LastActiveWidget->Leave(eventData); + } + this->LastActiveWidget = activeWidget; + if (!activeWidget) + { + // deactivate widget if we move far from it + if (eventid == vtkCommand::MouseMoveEvent && this->LastActiveWidget != nullptr) + { + this->LastActiveWidget->Leave(eventData); + this->LastActiveWidget = nullptr; + } + return false; + } + + // Pass on the interaction event to the active widget + return activeWidget->ProcessInteractionEvent(eventData); +} + +//--------------------------------------------------------------------------- +vtkMRMLMarkupsNode* vtkMRMLGUIWidgetsDisplayableManager::GetActiveMarkupsNodeForPlacement() +{ + vtkMRMLSelectionNode *selectionNode = this->GetSelectionNode(); + if (!selectionNode) + { + return nullptr; + } + const char *activeMarkupsID = selectionNode->GetActivePlaceNodeID(); + vtkMRMLMarkupsNode *markupsNode = vtkMRMLMarkupsNode::SafeDownCast(this->GetMRMLScene()->GetNodeByID(activeMarkupsID)); + if (!markupsNode) + { + return nullptr; + } + // Additional checks for placement + const char* placeNodeClassName = selectionNode->GetActivePlaceNodeClassName(); + if (!placeNodeClassName) + { + return nullptr; + } + if (!this->IsManageable(placeNodeClassName)) + { + return nullptr; + } + if (std::string(markupsNode->GetClassName()) != placeNodeClassName) + { + return nullptr; + } + return markupsNode; +} + +//--------------------------------------------------------------------------- +int vtkMRMLGUIWidgetsDisplayableManager::GetCurrentInteractionMode() +{ + vtkMRMLInteractionNode *interactionNode = this->GetInteractionNode(); + if (!interactionNode) + { + return 0; + } + return interactionNode->GetCurrentInteractionMode(); +} + +//--------------------------------------------------------------------------- +vtkSlicerMarkupsWidget* vtkMRMLGUIWidgetsDisplayableManager::GetWidgetForPlacement() +{ + if (this->GetCurrentInteractionMode() != vtkMRMLInteractionNode::Place) + { + return nullptr; + } + vtkMRMLSelectionNode *selectionNode = this->GetSelectionNode(); + if (!selectionNode) + { + return nullptr; + } + std::string placeNodeClassName = (selectionNode->GetActivePlaceNodeClassName() ? selectionNode->GetActivePlaceNodeClassName() : nullptr); + if (!this->IsManageable(placeNodeClassName.c_str())) + { + return nullptr; + } + + // Check if the active markups node is already the right class, and if yes then use that + vtkMRMLMarkupsNode *activeMarkupsNode = this->GetActiveMarkupsNodeForPlacement(); + + // Do not create a new widget if the markup is not displayable in this view + if (activeMarkupsNode) + { + bool canPlaceInThisView = false; + int numberOfDisplayNodes = activeMarkupsNode->GetNumberOfDisplayNodes(); + vtkMRMLAbstractViewNode* viewNode = vtkMRMLAbstractViewNode::SafeDownCast(this->GetMRMLDisplayableNode()); + for (int displayNodeIndex = 0; displayNodeIndex < numberOfDisplayNodes; displayNodeIndex++) + { + vtkMRMLDisplayNode* displayNode = activeMarkupsNode->GetNthDisplayNode(displayNodeIndex); + if (displayNode && displayNode->IsDisplayableInView(viewNode->GetID())) + { + canPlaceInThisView = true; + break; + } + } + if (!canPlaceInThisView) + { + return nullptr; + } + } + + if (activeMarkupsNode && activeMarkupsNode->GetMaximumNumberOfControlPoints() > 0 + && activeMarkupsNode->GetNumberOfControlPoints() >= activeMarkupsNode->GetMaximumNumberOfControlPoints()) + { + // maybe reached maximum number of points - if yes, then create a new widget + if (activeMarkupsNode->GetNumberOfControlPoints() == activeMarkupsNode->GetMaximumNumberOfControlPoints()) + { + // one more point than the maximum + vtkSlicerMarkupsWidget *slicerWidget = this->Internal->GetWidget(activeMarkupsNode); + if (slicerWidget && !slicerWidget->IsPointPreviewed()) + { + // no preview is shown, so the widget is actually complete + activeMarkupsNode = nullptr; + } + } + else + { + // clearly over the maximum number of points + activeMarkupsNode = nullptr; + } + } + + // If there is no active markups node then create a new one + if (!activeMarkupsNode) + { + activeMarkupsNode = this->CreateNewMarkupsNode(placeNodeClassName); + selectionNode->SetReferenceActivePlaceNodeID(activeMarkupsNode->GetID()); + } + + if (!activeMarkupsNode) + { + return nullptr; + } + vtkSlicerMarkupsWidget *slicerWidget = this->Internal->GetWidget(activeMarkupsNode); + return slicerWidget; +} + +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::SetHasFocus(bool hasFocus) +{ + if (!hasFocus && this->LastActiveWidget!=nullptr) + { + this->LastActiveWidget->Leave(nullptr); + this->LastActiveWidget = nullptr; + } +} + +//--------------------------------------------------------------------------- +bool vtkMRMLGUIWidgetsDisplayableManager::GetGrabFocus() +{ + return (this->LastActiveWidget != nullptr && this->LastActiveWidget->GetGrabFocus()); +} + +//--------------------------------------------------------------------------- +bool vtkMRMLGUIWidgetsDisplayableManager::GetInteractive() +{ + return (this->LastActiveWidget != nullptr && this->LastActiveWidget->GetInteractive()); +} + +//--------------------------------------------------------------------------- +int vtkMRMLGUIWidgetsDisplayableManager::GetMouseCursor() +{ + if (!this->LastActiveWidget) + { + return VTK_CURSOR_DEFAULT; + } + return this->LastActiveWidget->GetMouseCursor(); +} +*/ +//--------------------------------------------------------------------------- +vtkSlicerQWidgetWidget* vtkMRMLGUIWidgetsDisplayableManager::CreateWidget(vtkMRMLGUIWidgetDisplayNode* widgetDisplayNode) +{ + vtkSlicerQWidgetWidget* widget = vtkSlicerQWidgetWidget::New(); + + //TODO: Display node is not used, remove from attribute? + + // If the widget was successfully created + if (widget) + { + vtkMRMLAbstractViewNode* viewNode = vtkMRMLAbstractViewNode::SafeDownCast(this->GetMRMLDisplayableNode()); + vtkRenderer* renderer = this->GetRenderer(); + widget->SetMRMLApplicationLogic(this->GetMRMLApplicationLogic()); + widget->CreateDefaultRepresentation(/*widgetDisplayNode, */viewNode, renderer); + } + + return widget; +} +/* +//--------------------------------------------------------------------------- +void vtkMRMLGUIWidgetsDisplayableManager::ConvertDeviceToXYZ(double x, double y, double xyz[3]) +{ + vtkMRMLAbstractSliceViewDisplayableManager::ConvertDeviceToXYZ(this->GetInteractor(), this->GetMRMLSliceNode(), x, y, xyz); +} +*/ diff --git a/GUIWidgets/MRMLDM/vtkMRMLGUIWidgetsDisplayableManager.h b/GUIWidgets/MRMLDM/vtkMRMLGUIWidgetsDisplayableManager.h new file mode 100644 index 0000000..51f24c7 --- /dev/null +++ b/GUIWidgets/MRMLDM/vtkMRMLGUIWidgetsDisplayableManager.h @@ -0,0 +1,177 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +#ifndef __vtkMRMLGUIWidgetsDisplayableManager_h +#define __vtkMRMLGUIWidgetsDisplayableManager_h + +// GUIWidget includes +#include "vtkSlicerGUIWidgetsModuleMRMLDisplayableManagerExport.h" + +// GUIWidgetsModule/MRMLDisplayableManager includes +//#include "vtkMRMLGUIWidgetsDisplayableManagerHelper.h" + +// MRMLDisplayableManager includes +#include + +#include + +// STD includes +#include + +class vtkMRMLGUIWidgetNode; +class vtkSlicerViewerWidget; +class vtkMRMLGUIWidgetDisplayNode; +class vtkAbstractWidget; + +/// \ingroup Slicer_QtModules_GUIWidgets +class VTK_SLICER_GUIWIDGETS_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLGUIWidgetsDisplayableManager + : public vtkMRMLAbstractDisplayableManager +{ +public: + + // Allow the helper to call protected methods of displayable manager + //friend class vtkMRMLGUIWidgetsDisplayableManagerHelper; + + static vtkMRMLGUIWidgetsDisplayableManager *New(); + vtkTypeMacro(vtkMRMLGUIWidgetsDisplayableManager, vtkMRMLAbstractDisplayableManager); + void PrintSelf(ostream& os, vtkIndent indent) override; + /* + /// Check if this is a 2d SliceView displayable manager, returns true if so, + /// false otherwise. Checks return from GetSliceNode for non null, which means + /// it's a 2d displayable manager + virtual bool Is2DDisplayableManager(); + + /// Get the sliceNode, if registered. This would mean it is a 2D SliceView displayableManager. + vtkMRMLSliceNode * GetMRMLSliceNode(); + + vtkMRMLGUIWidgetsDisplayableManagerHelper * GetHelper() { return this->Helper; }; + + bool CanProcessInteractionEvent(vtkMRMLInteractionEventData* eventData, double &closestDistance2) override; + bool ProcessInteractionEvent(vtkMRMLInteractionEventData* eventData) override; + + void SetHasFocus(bool hasFocus) override; + bool GetGrabFocus() override; + bool GetInteractive() override; + int GetMouseCursor() override; + + // Updates markup point preview position. + // Returns true if the event is processed. + vtkSlicerMarkupsWidget* GetWidgetForPlacement(); + + vtkMRMLMarkupsNode* GetActiveMarkupsNodeForPlacement(); + + int GetCurrentInteractionMode(); + + // Methods from vtkMRMLAbstractSliceViewDisplayableManager + + /// Convert device coordinates (display) to XYZ coordinates (viewport). + /// Parameter \a xyz is double[3] + /// \sa ConvertDeviceToXYZ(vtkRenderWindowInteractor *, vtkMRMLSliceNode *, double x, double y, double xyz[3]) + void ConvertDeviceToXYZ(double x, double y, double xyz[3]); + + /// Get the widget of a node. + vtkSlicerMarkupsWidget* GetWidget(vtkMRMLMarkupsDisplayNode * node); + */ +protected: + + vtkMRMLGUIWidgetsDisplayableManager(); + ~vtkMRMLGUIWidgetsDisplayableManager() override; + /* + vtkSlicerMarkupsWidget* FindClosestWidget(vtkMRMLInteractionEventData *callData, double &closestDistance2); + */ + void ProcessMRMLNodesEvents(vtkObject *caller, unsigned long event, void *callData) override; + + /// Wrap the superclass render request in a check for batch processing + virtual void RequestRender(); + + /// Called from RequestRender method if UpdateFromMRMLRequested is true + /// \sa RequestRender() SetUpdateFromMRMLRequested() + void UpdateFromMRML() override; + + void SetMRMLSceneInternal(vtkMRMLScene* newScene) override; + + /// Called after the corresponding MRML event is triggered, from AbstractDisplayableManager + /// \sa ProcessMRMLSceneEvents + void UpdateFromMRMLScene() override; + void OnMRMLSceneEndClose() override; + void OnMRMLSceneEndImport() override; + void OnMRMLSceneNodeAdded(vtkMRMLNode* node) override; + void OnMRMLSceneNodeRemoved(vtkMRMLNode* node) override; + + /// Create a widget. + vtkSlicerQWidgetWidget* CreateWidget(vtkMRMLGUIWidgetDisplayNode* node); + + /// Called after the corresponding MRML View container was modified + void OnMRMLDisplayableNodeModifiedEvent(vtkObject* caller) override; + /* + /// Handler for specific SliceView actions, iterate over the widgets in the helper + virtual void OnMRMLSliceNodeModifiedEvent(); + + /// Observe the interaction node. + void AddObserversToInteractionNode(); + void RemoveObserversFromInteractionNode(); + + /// Check if it is the right displayManager + virtual bool IsCorrectDisplayableManager(); + + /// Return true if this displayable manager supports(can manage) that node, + /// false otherwise. + /// Can be reimplemented to add more conditions. + /// \sa IsManageable(const char*), IsCorrectDisplayableManager() + virtual bool IsManageable(vtkMRMLNode* node); + /// Return true if this displayable manager supports(can manage) that node class, + /// false otherwise. + /// Can be reimplemented to add more conditions. + /// \sa IsManageable(vtkMRMLNode*), IsCorrectDisplayableManager() + virtual bool IsManageable(const char* nodeClassName); + + /// Respond to interactor style events + void OnInteractorStyleEvent(int eventid) override; + + /// Accessor for internal flag that disables interactor style event processing + vtkGetMacro(DisableInteractorStyleEventsProcessing, int); + + vtkSmartPointer Helper; + + double LastClickWorldCoordinates[4]; + + vtkMRMLMarkupsNode* CreateNewMarkupsNode(const std::string &markupsNodeClassName); + + vtkWeakPointer LastActiveWidget; + */ +private: + vtkMRMLGUIWidgetsDisplayableManager(const vtkMRMLGUIWidgetsDisplayableManager&) = delete; + void operator=(const vtkMRMLGUIWidgetsDisplayableManager&) = delete; + /* + int DisableInteractorStyleEventsProcessing; + + // by default, this displayableManager handles a 2d view, so the SliceNode + // must be set when it's assigned to a viewer + vtkWeakPointer SliceNode; + */ + + class vtkInternal; + vtkInternal* Internal; + friend class vtkInternal; +}; + +#endif diff --git a/GUIWidgets/Resources/Icons/GUIWidgets.png b/GUIWidgets/Resources/Icons/GUIWidgets.png new file mode 100644 index 0000000000000000000000000000000000000000..9c5938ea6d9b8b44ce19e091d7f36d34f3733eb9 GIT binary patch literal 20628 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 + + qSlicerGUIWidgetsModuleWidget + + + + 0 + 0 + 342 + 514 + + + + vtkQWidgetWidget test + + + + + + Test GUIWidget + + + + + + Add GUI widget node with Hello World push button + + + + + + + + + + + + To test rendering update on change in widget + + + Update button label + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Add GUI widget node with Home widget + + + + + + + Add GUI widget node with Data Module widget + + + + + + + Add GUI widget node with Segment Editor widget + + + + + + + Add GUI widget node with Transform widget + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + VR pointer development + + + + + + Set up interaction + + + + + + + Start interaction + + + + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + qSlicerWidget + QWidget +
qSlicerWidget.h
+ 1 +
+ + ctkCollapsibleButton + QWidget +
ctkCollapsibleButton.h
+ 1 +
+
+ + +
diff --git a/GUIWidgets/Resources/qSlicerGUIWidgetsModule.qrc b/GUIWidgets/Resources/qSlicerGUIWidgetsModule.qrc new file mode 100644 index 0000000..f5e54d6 --- /dev/null +++ b/GUIWidgets/Resources/qSlicerGUIWidgetsModule.qrc @@ -0,0 +1,5 @@ + + + Icons/GUIWidgets.png + + diff --git a/GUIWidgets/Testing/CMakeLists.txt b/GUIWidgets/Testing/CMakeLists.txt new file mode 100644 index 0000000..35f9732 --- /dev/null +++ b/GUIWidgets/Testing/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Cxx) diff --git a/GUIWidgets/Testing/Cxx/CMakeLists.txt b/GUIWidgets/Testing/Cxx/CMakeLists.txt new file mode 100644 index 0000000..b7341cd --- /dev/null +++ b/GUIWidgets/Testing/Cxx/CMakeLists.txt @@ -0,0 +1,17 @@ +set(KIT qSlicer${MODULE_NAME}Module) + +#----------------------------------------------------------------------------- +set(KIT_TEST_SRCS + #qSlicer${MODULE_NAME}ModuleTest.cxx + ) + +#----------------------------------------------------------------------------- +slicerMacroConfigureModuleCxxTestDriver( + NAME ${KIT} + SOURCES ${KIT_TEST_SRCS} + WITH_VTK_DEBUG_LEAKS_CHECK + WITH_VTK_ERROR_OUTPUT_CHECK + ) + +#----------------------------------------------------------------------------- +#simple_test(qSlicer${MODULE_NAME}ModuleTest) diff --git a/GUIWidgets/VTKWidgets/CMakeLists.txt b/GUIWidgets/VTKWidgets/CMakeLists.txt new file mode 100644 index 0000000..2dcd655 --- /dev/null +++ b/GUIWidgets/VTKWidgets/CMakeLists.txt @@ -0,0 +1,34 @@ +project(vtkSlicer${MODULE_NAME}ModuleVTKWidgets) + +set(KIT ${PROJECT_NAME}) + +set(${KIT}_EXPORT_DIRECTIVE "VTK_SLICER_${MODULE_NAME_UPPER}_MODULE_VTKWIDGETS_EXPORT") + +set(${KIT}_INCLUDE_DIRECTORIES + ${vtkSlicer${MODULE_NAME}ModuleMRML_SOURCE_DIR} + ${vtkSlicer${MODULE_NAME}ModuleMRML_BINARY_DIR} + ${vtkSlicerMarkupsModuleVTKWidgets_INCLUDE_DIRS} + ) + +set(${KIT}_SRCS + vtkSlicerQWidgetRepresentation.cxx + vtkSlicerQWidgetRepresentation.h + vtkSlicerQWidgetTexture.cxx + vtkSlicerQWidgetTexture.h + vtkSlicerQWidgetWidget.cxx + vtkSlicerQWidgetWidget.h + ) + +set(${KIT}_TARGET_LIBRARIES + vtkSlicer${MODULE_NAME}ModuleMRML + vtkSlicerMarkupsModuleVTKWidgets + ) + +#----------------------------------------------------------------------------- +SlicerMacroBuildModuleLogic( + NAME ${KIT} + EXPORT_DIRECTIVE ${${KIT}_EXPORT_DIRECTIVE} + INCLUDE_DIRECTORIES ${${KIT}_INCLUDE_DIRECTORIES} + SRCS ${${KIT}_SRCS} + TARGET_LIBRARIES ${${KIT}_TARGET_LIBRARIES} + ) diff --git a/GUIWidgets/VTKWidgets/vtkSlicerQWidgetRepresentation.cxx b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetRepresentation.cxx new file mode 100644 index 0000000..856a999 --- /dev/null +++ b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetRepresentation.cxx @@ -0,0 +1,274 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +#include "vtkSlicerQWidgetRepresentation.h" + +#include "vtkSlicerQWidgetTexture.h" + +// GUI Widget includes +#include "vtkMRMLGUIWidgetNode.h" +#include "vtkMRMLGUIWidgetDisplayNode.h" + +// VTK includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Qt includes +#include +#include + +vtkStandardNewMacro(vtkSlicerQWidgetRepresentation); + +//------------------------------------------------------------------------------ +vtkSlicerQWidgetRepresentation::vtkSlicerQWidgetRepresentation() +{ + this->PlaneSource = vtkPlaneSource::New(); + this->PlaneSource->SetOutputPointsPrecision(vtkAlgorithm::DOUBLE_PRECISION); + + this->PlaneMapper = vtkPolyDataMapper::New(); + this->PlaneMapper->SetInputConnection(this->PlaneSource->GetOutputPort()); + + this->TextureCallbackCommand = vtkCallbackCommand::New(); + this->TextureCallbackCommand->SetClientData( reinterpret_cast(this) ); + this->TextureCallbackCommand->SetCallback( vtkSlicerQWidgetRepresentation::OnTextureModified ); + + this->QWidgetTexture = vtkSlicerQWidgetTexture::New(); + this->QWidgetTexture->AddObserver(vtkCommand::ModifiedEvent, this->TextureCallbackCommand); + + this->PlaneActor = vtkActor::New(); + this->PlaneActor->SetMapper(this->PlaneMapper); + this->PlaneActor->SetTexture(this->QWidgetTexture); + + this->PlaneActor->GetProperty()->SetAmbient(1.0); + this->PlaneActor->GetProperty()->SetDiffuse(0.0); + + // Define the point coordinates + double bounds[6] = {-80, 80, -0.5, 0.5, -50, 50 }; // Width, Depth, Height + + // Initial creation of the widget, serves to initialize it + this->PlaceWidget(bounds); +} + +//------------------------------------------------------------------------------ +vtkSlicerQWidgetRepresentation::~vtkSlicerQWidgetRepresentation() +{ + if (this->PlaneSource) + { + this->PlaneSource->Delete(); + this->PlaneSource = nullptr; + } + if (this->PlaneMapper) + { + this->PlaneMapper->Delete(); + this->PlaneMapper = nullptr; + } + if (this->PlaneActor) + { + this->PlaneActor->Delete(); + this->PlaneActor = nullptr; + } + + if (this->QWidgetTexture) + { + this->QWidgetTexture->RemoveObserver(this->TextureCallbackCommand); + this->QWidgetTexture->Delete(); + this->QWidgetTexture = nullptr; + } + if (this->TextureCallbackCommand) + { + this->TextureCallbackCommand->SetClientData(nullptr); + this->TextureCallbackCommand->Delete(); + this->TextureCallbackCommand = nullptr; + } +} + +//------------------------------------------------------------------------------ +void vtkSlicerQWidgetRepresentation::SetWidget(QWidget* w) +{ + // just pass down to the QWidgetTexture + this->QWidgetTexture->SetWidget(w); + this->Modified(); +} + +//------------------------------------------------------------------------------ +double* vtkSlicerQWidgetRepresentation::GetBounds() +{ + //this->BuildRepresentation(); + return this->PlaneActor->GetBounds(); +} + +//------------------------------------------------------------------------------ +void vtkSlicerQWidgetRepresentation::GetActors(vtkPropCollection* pc) +{ + this->Superclass::GetActors(pc); + this->PlaneActor->GetActors(pc); +} + +//------------------------------------------------------------------------------ +void vtkSlicerQWidgetRepresentation::ReleaseGraphicsResources(vtkWindow* w) +{ + this->Superclass::ReleaseGraphicsResources(w); + this->PlaneActor->ReleaseGraphicsResources(w); + this->PlaneMapper->ReleaseGraphicsResources(w); + this->QWidgetTexture->ReleaseGraphicsResources(w); +} + +//------------------------------------------------------------------------------ +int vtkSlicerQWidgetRepresentation::RenderOpaqueGeometry(vtkViewport* v) +{ + int count = this->Superclass::RenderOpaqueGeometry(v); + + if (this->PlaneActor->GetVisibility()) + { + this->PlaneActor->SetPropertyKeys(this->GetPropertyKeys()); + + count += this->PlaneActor->RenderOpaqueGeometry(v); + } + + return count; +} + +//------------------------------------------------------------------------------ +int vtkSlicerQWidgetRepresentation::RenderTranslucentPolygonalGeometry(vtkViewport* viewport) +{ + int count=0; + count = this->Superclass::RenderTranslucentPolygonalGeometry(viewport); + if (this->PlaneActor->GetVisibility()) + { + // The internal actor needs to share property keys. + // This ensures the mapper state is consistent and allows depth peeling to work as expected. + this->PlaneActor->SetPropertyKeys(this->GetPropertyKeys()); + + count += this->PlaneActor->RenderTranslucentPolygonalGeometry(viewport); + } + return count; +} + +//------------------------------------------------------------------------------ +vtkTypeBool vtkSlicerQWidgetRepresentation::HasTranslucentPolygonalGeometry() +{ + if (this->Superclass::HasTranslucentPolygonalGeometry()) + { + return true; + } + if (this->PlaneActor->GetVisibility() && this->PlaneActor->HasTranslucentPolygonalGeometry()) + { + return true; + } + return false; +} + +//------------------------------------------------------------------------------ +void vtkSlicerQWidgetRepresentation::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + + // this->InteractionState is printed in superclass + // this is commented to avoid PrintSelf errors +} + +//------------------------------------------------------------------------------ +void vtkSlicerQWidgetRepresentation::PlaceWidget(double bds[6]) +{ + this->PlaneSource->SetOrigin(bds[0], bds[2], bds[4]); + this->PlaneSource->SetPoint1(bds[1], bds[2], bds[4]); + this->PlaneSource->SetPoint2(bds[0], bds[2], bds[5]); +} + +//---------------------------------------------------------------------- +void vtkSlicerQWidgetRepresentation::UpdateFromMRML(vtkMRMLNode* caller, unsigned long event, void *callData /*=nullptr*/) +{ + Superclass::UpdateFromMRML(caller, event, callData); + + this->NeedToRenderOn(); + + vtkMRMLGUIWidgetNode* guiWidgetNode = vtkMRMLGUIWidgetNode::SafeDownCast(this->GetMarkupsNode()); + vtkMRMLGUIWidgetDisplayNode* displayNode = vtkMRMLGUIWidgetDisplayNode::SafeDownCast(this->GetMarkupsDisplayNode()); + if (!guiWidgetNode || !this->IsDisplayable() || !displayNode) + { + this->VisibilityOff(); + return; + } + + if (guiWidgetNode->GetWidget() != this->GetQWidgetTexture()->GetWidget()) + { + if (guiWidgetNode->GetWidget() == nullptr) + { + this->QWidgetTexture->SetWidget(nullptr); + } + else + { + QWidget* widget = reinterpret_cast(guiWidgetNode->GetWidget()); + if (widget != this->QWidgetTexture->GetWidget()) + { + this->QWidgetTexture->SetWidget(widget); + } + } + } + + if (!this->QWidgetTexture->GetWidget() || !this->ViewNode) + { + this->VisibilityOff(); + this->PlaneActor->SetVisibility(false); + return; + } + + this->VisibilityOn(); + this->PlaneActor->SetVisibility(true); +} + +//--------------------------------------------------------------------------- +void vtkSlicerQWidgetRepresentation::OnTextureModified( + vtkObject* vtkNotUsed(caller), unsigned long vtkNotUsed(eid), void* clientData, void* vtkNotUsed(callData)) +{ + vtkSlicerQWidgetRepresentation* self = reinterpret_cast(clientData); + + // Redefine widget plane + QWidget* widget = self->QWidgetTexture->GetWidget(); + if (!widget) + { + return; + } + + QRect rect = widget->geometry(); + if (rect.width() < 2 || rect.height() < 2) + { + return; + } + double bounds[6] = { + -(double)(rect.width()/2)*self->SpacingMmPerPixel, (double)rect.width()/2*self->SpacingMmPerPixel, + -0.5, 0.5, + -(double)(rect.height()/2)*self->SpacingMmPerPixel, (double)rect.height()/2*self->SpacingMmPerPixel + }; + self->PlaceWidget(bounds); + + // Trigger rendering in view + self->GetViewNode()->Modified(); +} diff --git a/GUIWidgets/VTKWidgets/vtkSlicerQWidgetRepresentation.h b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetRepresentation.h new file mode 100644 index 0000000..79d5c5a --- /dev/null +++ b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetRepresentation.h @@ -0,0 +1,132 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +/** + * @class vtkSlicerQWidgetRepresentation + * @brief a class defining the representation for a vtkSlicerQWidgetWidget + * + * This class renders a QWidget as a simple vtkPlaneSource with a vtkTexture + * that contains a vtkSlicerQWidgetTexture which imports the OpenGL texture handle + * from Qt into the vtk scene. Qt and VTK may need to be using the same + * graphics context. + */ + +#ifndef vtkSlicerQWidgetRepresentation_h +#define vtkSlicerQWidgetRepresentation_h + +#include "vtkSlicerGUIWidgetsModuleVTKWidgetsExport.h" + +#include "vtkSlicerMarkupsWidgetRepresentation3D.h" + +class QWidget; + +class vtkActor; +class vtkCallbackCommand; +class vtkOpenGLTexture; +class vtkPlaneSource; +class vtkPolyDataAlgorithm; +class vtkPolyDataMapper; +class vtkSlicerQWidgetTexture; + +class VTK_SLICER_GUIWIDGETS_MODULE_VTKWIDGETS_EXPORT vtkSlicerQWidgetRepresentation : public vtkSlicerMarkupsWidgetRepresentation3D +{ +public: + /** + * Instantiate the class. + */ + static vtkSlicerQWidgetRepresentation* New(); + + ///@{ + /// Standard methods for the class. + vtkTypeMacro(vtkSlicerQWidgetRepresentation, vtkSlicerMarkupsWidgetRepresentation3D); + void PrintSelf(ostream& os, vtkIndent indent) override; + ///@} + + /// Subclasses of vtkMRMLAbstractWidgetRepresentation must implement these methods. These + /// are the methods that the widget and its representation use to + /// communicate with each other. + void UpdateFromMRML(vtkMRMLNode* caller, unsigned long event, void *callData=nullptr) override; + + /// TODO: + void PlaceWidget(double bounds[6]); + + ///@{ + /// Methods supporting the rendering process. + double* GetBounds() VTK_SIZEHINT(6) override; + void GetActors(vtkPropCollection* pc) override; + void ReleaseGraphicsResources(vtkWindow*) override; + int RenderOpaqueGeometry(vtkViewport*) override; + int RenderTranslucentPolygonalGeometry(vtkViewport*) override; + vtkTypeBool HasTranslucentPolygonalGeometry() override; + ///@} + + // Manage the state of the widget + enum _InteractionState + { + Outside = 0, + Inside + }; + + /// Set the QWidget this representation will render + void SetWidget(QWidget* w); + + /// Get the QWidgetTexture used by the representation + vtkGetObjectMacro(QWidgetTexture, vtkSlicerQWidgetTexture); + + /// Get the vtkPlaneSouce used by this representation. This can be useful + /// to set the Origin, Point1, Point2 of the plane source directly. + vtkGetObjectMacro(PlaneSource, vtkPlaneSource); + + /// Get the widget coordinates as computed in the last call to + /// ComputeComplexInteractionState. + //vtkGetVector2Macro(WidgetCoordinates, int); + + /// Get spacing of the widget (mm/pixel) + vtkGetMacro(SpacingMmPerPixel, double); + /// Set spacing of the widget (mm/pixel) + vtkSetMacro(SpacingMmPerPixel, double); + +protected: + /// Callback function observing texture modified events. + static void OnTextureModified(vtkObject* caller, unsigned long eid, void* clientData, void* callData); + +protected: + //int WidgetCoordinates[2]; + double SpacingMmPerPixel{0.5}; + + vtkPlaneSource* PlaneSource; + vtkPolyDataMapper* PlaneMapper; + vtkActor* PlaneActor; + vtkOpenGLTexture* PlaneTexture; + vtkSlicerQWidgetTexture* QWidgetTexture; + vtkCallbackCommand* TextureCallbackCommand; + +protected: + vtkSlicerQWidgetRepresentation(); + ~vtkSlicerQWidgetRepresentation() override; + +private: + vtkSlicerQWidgetRepresentation(const vtkSlicerQWidgetRepresentation&) = delete; + void operator=(const vtkSlicerQWidgetRepresentation&) = delete; +}; + +#endif diff --git a/GUIWidgets/VTKWidgets/vtkSlicerQWidgetTexture.cxx b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetTexture.cxx new file mode 100644 index 0000000..689716a --- /dev/null +++ b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetTexture.cxx @@ -0,0 +1,126 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +#include "vtkSlicerQWidgetTexture.h" + +// SlicerQt includes +#include "qMRMLUtils.h" +#include "qSlicerCoreApplication.h" + +// VTK includes +#include + +// Qt includes +#include +#include +#include +#include + +//------------------------------------------------------------------------------ +vtkStandardNewMacro(vtkSlicerQWidgetTexture); + +//------------------------------------------------------------------------------ +vtkSlicerQWidgetTexture::vtkSlicerQWidgetTexture() +{ + this->Scene = nullptr; + this->Widget = nullptr; + + this->UpdateTextureMethod = [this]() { + if (!this->Widget) + { + return; + } + QImage grabImage(this->Widget->grab().toImage()); + qMRMLUtils::qImageToVtkImageData(grabImage, this->TextureImageData.GetPointer()); + this->Modified(); + }; +} + +//------------------------------------------------------------------------------ +vtkSlicerQWidgetTexture::~vtkSlicerQWidgetTexture() +{ + this->SetWidget(nullptr); + delete this->Scene; + this->Scene = nullptr; +} + +//------------------------------------------------------------------------------ +void vtkSlicerQWidgetTexture::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +//------------------------------------------------------------------------------ +void vtkSlicerQWidgetTexture::ReleaseGraphicsResources(vtkWindow* win) +{ + this->Superclass::ReleaseGraphicsResources(win); +} + +//------------------------------------------------------------------------------ +void vtkSlicerQWidgetTexture::SetWidget(QWidget* w) +{ + if (this->Widget == w) + { + return; + } + + if (w == nullptr && this->Scene && this->Widget->graphicsProxyWidget()) + { + this->Scene->removeItem(this->Widget->graphicsProxyWidget()); + } + + this->Widget = w; + + this->SetupWidget(); + + this->Modified(); +} + +//------------------------------------------------------------------------------ +void vtkSlicerQWidgetTexture::SetupWidget() +{ + if (!this->Widget) + { + return; + } + + this->Scene = new QGraphicsScene(); + + this->Widget->move(0, 0); + this->Scene->addWidget(this->Widget); + + QObject::connect(this->Scene, &QGraphicsScene::changed, this->UpdateTextureMethod); + QObject::connect(this->Widget, &QObject::objectNameChanged, this->UpdateTextureMethod); //TODO: For debugging + + if (this->TextureImageData.GetPointer() == nullptr) + { + this->TextureImageData = vtkSmartPointer::New(); + } + if (this->TextureTrivialProducer.GetPointer() == nullptr) + { + this->TextureTrivialProducer = vtkSmartPointer::New(); + this->TextureTrivialProducer->SetOutput(this->TextureImageData); + this->SetInputConnection(this->TextureTrivialProducer->GetOutputPort()); + } + + this->UpdateTextureMethod(); +} diff --git a/GUIWidgets/VTKWidgets/vtkSlicerQWidgetTexture.h b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetTexture.h new file mode 100644 index 0000000..d5ecdc2 --- /dev/null +++ b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetTexture.h @@ -0,0 +1,95 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +#ifndef vtkSlicerQWidgetTexture_h +#define vtkSlicerQWidgetTexture_h + +#include "vtkSlicerGUIWidgetsModuleVTKWidgetsExport.h" + +// VTK includes +#include +#include +#include +#include + +#include // for ivar + +class QGraphicsScene; +class QImage; +class QWidget; + +/** + * @class vtkSlicerQWidgetTexture + * @brief Allows a QWidget to be used as a texture in VTK with OpenGL + * + * This class works by rendering the QWidget into a Framebuffer + * and then sending the OpenGL texture handle to VTK for rendering. + */ +class VTK_SLICER_GUIWIDGETS_MODULE_VTKWIDGETS_EXPORT vtkSlicerQWidgetTexture : public vtkOpenGLTexture +{ +public: + static vtkSlicerQWidgetTexture* New(); + vtkTypeMacro(vtkSlicerQWidgetTexture, vtkOpenGLTexture); + void PrintSelf(ostream& os, vtkIndent indent) override; + + ///@{ + /** + * Set/Get the QWidget that this TextureObject will render/use. + * Just hold onto the widget until opengl context is active. + */ + void SetWidget(QWidget* w); + QWidget* GetWidget() { return this->Widget; } + ///@} + + /** + * get the QScene used for rendering, this is where events will + * be forwarded to. + */ + QGraphicsScene* GetScene() { return this->Scene; } + + /** + * Free resources + */ + void ReleaseGraphicsResources(vtkWindow* win) override; + +protected: + vtkSlicerQWidgetTexture(); + ~vtkSlicerQWidgetTexture() override; + + QGraphicsScene* Scene; + QWidget* Widget; + + vtkSmartPointer TextureImageData; + vtkSmartPointer TextureTrivialProducer; + + /// method called when the widget needs repainting + std::function UpdateTextureMethod; + + /// Setup new widget with the graphics scene observation + void SetupWidget(); + +private: + vtkSlicerQWidgetTexture(const vtkSlicerQWidgetTexture&) = delete; + void operator=(const vtkSlicerQWidgetTexture&) = delete; +}; + +#endif diff --git a/GUIWidgets/VTKWidgets/vtkSlicerQWidgetWidget.cxx b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetWidget.cxx new file mode 100644 index 0000000..206369e --- /dev/null +++ b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetWidget.cxx @@ -0,0 +1,121 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +#include "vtkSlicerQWidgetWidget.h" + +#include "vtkMRMLGUIWidgetNode.h" + +#include "vtkSlicerQWidgetRepresentation.h" +#include "vtkSlicerQWidgetTexture.h" + +// MRML includes +#include "vtkMRMLSliceNode.h" + +// Qt includes +#include +#include +#include +#include +#include +#include + +// VTK includes +#include "vtkCallbackCommand.h" +// #include "vtkCommand.h" +#include "vtkEvent.h" +#include "vtkEventData.h" +#include "vtkObjectFactory.h" +#include "vtkRenderWindow.h" +#include "vtkRenderWindowInteractor.h" +#include "vtkRenderer.h" +#include "vtkWidgetCallbackMapper.h" +#include "vtkWidgetEvent.h" +#include "vtkWidgetEventTranslator.h" + +vtkStandardNewMacro(vtkSlicerQWidgetWidget); + +//------------------------------------------------------------------------------ +vtkSlicerQWidgetWidget::vtkSlicerQWidgetWidget() +{ +} + +//------------------------------------------------------------------------------ +vtkSlicerQWidgetWidget::~vtkSlicerQWidgetWidget() = default; + +//------------------------------------------------------------------------------ +vtkSlicerQWidgetRepresentation* vtkSlicerQWidgetWidget::GetQWidgetRepresentation() +{ + return vtkSlicerQWidgetRepresentation::SafeDownCast(this->WidgetRep); +} + +//------------------------------------------------------------------------------ +vtkSlicerMarkupsWidget* vtkSlicerQWidgetWidget::CreateInstance()const +{ + vtkObject* ret = vtkObjectFactory::CreateInstance("vtkSlicerQWidgetWidget"); + if(ret) + { + return static_cast(ret); + } + + vtkSlicerQWidgetWidget* result = new vtkSlicerQWidgetWidget; +#ifdef VTK_HAS_INITIALIZE_OBJECT_BASE + result->InitializeObjectBase(); +#endif + return result; +} + +//---------------------------------------------------------------------- +void vtkSlicerQWidgetWidget::CreateDefaultRepresentation( + vtkMRMLMarkupsDisplayNode* markupsDisplayNode, vtkMRMLAbstractViewNode* viewNode, vtkRenderer* renderer) +{ + if (vtkMRMLSliceNode::SafeDownCast(viewNode)) + { + // There is no 2D representation of the GUI widget + return; + } + + vtkNew rep; + this->SetRenderer(renderer); + this->SetRepresentation(rep); + rep->SetMarkupsDisplayNode(markupsDisplayNode); + rep->SetViewNode(viewNode); + + rep->UpdateFromMRML(nullptr, 0); // full update +} + +//------------------------------------------------------------------------------ +void vtkSlicerQWidgetWidget::SetRepresentation(vtkMRMLAbstractWidgetRepresentation* rep) +{ + this->Superclass::SetRepresentation(rep); + + vtkSlicerQWidgetRepresentation* qWidgetRep = vtkSlicerQWidgetRepresentation::SafeDownCast(rep); + if (!qWidgetRep) + { + vtkErrorMacro("SetRepresentation: Given representation is not a vtkSlicerQWidgetRepresentation"); + } +} + +//------------------------------------------------------------------------------ +void vtkSlicerQWidgetWidget::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/GUIWidgets/VTKWidgets/vtkSlicerQWidgetWidget.h b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetWidget.h new file mode 100644 index 0000000..304a15d --- /dev/null +++ b/GUIWidgets/VTKWidgets/vtkSlicerQWidgetWidget.h @@ -0,0 +1,103 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +/** + * @class vtkSlicerQWidgetWidget + * @brief 3D VTK widget for a QWidget + * + * This 3D widget handles events between VTK and Qt for a QWidget placed + * in a scene. It currently takes 6dof events as from VR controllers and + * if they intersect the widget it converts them to Qt events and fires + * them off. + */ + +#ifndef vtkSlicerQWidgetWidget_h +#define vtkSlicerQWidgetWidget_h + +#include "vtkSlicerGUIWidgetsModuleVTKWidgetsExport.h" + +#include "vtkSlicerMarkupsWidget.h" + +// Qt includes +#include + +class vtkSlicerQWidgetRepresentation; + +class VTK_SLICER_GUIWIDGETS_MODULE_VTKWIDGETS_EXPORT vtkSlicerQWidgetWidget : public vtkSlicerMarkupsWidget +{ + friend class vtkInteractionCallback; + +public: + /// Instantiate the object. + static vtkSlicerQWidgetWidget* New(); + + ///@{ + /// Standard vtkObject methods + vtkTypeMacro(vtkSlicerQWidgetWidget, vtkSlicerMarkupsWidget); + void PrintSelf(ostream& os, vtkIndent indent) override; + ///@} + + /// Create instance of the markups widget + vtkSlicerMarkupsWidget* CreateInstance() const override; + + /// Specify an instance of vtkSlicerQWidgetRepresentation used to represent this + /// widget in the scene. Note that the representation is a subclass of vtkProp + /// so it can be added to the renderer independent of the widget. + void SetRepresentation(vtkMRMLAbstractWidgetRepresentation *r) override; + + // Description: + // Disable/Enable the widget if needed. + // Unobserved the camera if the widget is disabled. + //void SetEnabled(int enabling) override; + + /// Return the representation as a vtkSlicerQWidgetRepresentation + vtkSlicerQWidgetRepresentation* GetQWidgetRepresentation(); + + /// Create the default widget representation and initializes the widget and representation. + void CreateDefaultRepresentation( + vtkMRMLMarkupsDisplayNode* markupsDisplayNode, vtkMRMLAbstractViewNode* viewNode, vtkRenderer* renderer) override; + +protected: + vtkSlicerQWidgetWidget(); + ~vtkSlicerQWidgetWidget() override; + /* + // Manage the state of the widget + int WidgetState; + enum _WidgetState + { + Start = 0, + Active + }; + */ + QPointF LastWidgetCoordinates; + + // These methods handle events + //static void SelectAction3D(vtkAbstractWidget*); + //static void EndSelectAction3D(vtkAbstractWidget*); + //static void MoveAction3D(vtkAbstractWidget*); + +private: + vtkSlicerQWidgetWidget(const vtkSlicerQWidgetWidget&) = delete; + void operator=(const vtkSlicerQWidgetWidget&) = delete; +}; + +#endif diff --git a/GUIWidgets/Widgets/CMakeLists.txt b/GUIWidgets/Widgets/CMakeLists.txt new file mode 100644 index 0000000..8b95ade --- /dev/null +++ b/GUIWidgets/Widgets/CMakeLists.txt @@ -0,0 +1,38 @@ +project(qSlicer${MODULE_NAME}ModuleWidgets) + +set(KIT ${PROJECT_NAME}) + +set(${KIT}_EXPORT_DIRECTIVE "Q_SLICER_MODULE_${MODULE_NAME_UPPER}_WIDGETS_EXPORT") + +set(${KIT}_INCLUDE_DIRECTORIES + ) + +set(${KIT}_SRCS + ) + +set(${KIT}_MOC_SRCS + ) + +set(${KIT}_UI_SRCS + ) + +set(${KIT}_RESOURCES + ../Resources/qSlicer${MODULE_NAME}Module.qrc + ) + +set(${KIT}_TARGET_LIBRARIES + vtkSlicer${MODULE_NAME}ModuleLogic + ) + +#----------------------------------------------------------------------------- +SlicerMacroBuildModuleWidgets( + NAME ${KIT} + EXPORT_DIRECTIVE ${${KIT}_EXPORT_DIRECTIVE} + INCLUDE_DIRECTORIES ${${KIT}_INCLUDE_DIRECTORIES} + SRCS ${${KIT}_SRCS} + MOC_SRCS ${${KIT}_MOC_SRCS} + UI_SRCS ${${KIT}_UI_SRCS} + TARGET_LIBRARIES ${${KIT}_TARGET_LIBRARIES} + RESOURCES ${${KIT}_RESOURCES} + WRAP_PYTHONQT + ) diff --git a/GUIWidgets/qSlicerGUIWidgetsModule.cxx b/GUIWidgets/qSlicerGUIWidgetsModule.cxx new file mode 100644 index 0000000..22def63 --- /dev/null +++ b/GUIWidgets/qSlicerGUIWidgetsModule.cxx @@ -0,0 +1,157 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +// GUIWidgets MRML includes +#include + +// GUIWidgets Logic includes +#include + +// GUIWidgets VTKWidgets includes +#include + +// GUIWidgets includes +#include "qSlicerGUIWidgetsModule.h" +#include "qSlicerGUIWidgetsModuleWidget.h" + +// Markups Logic includes +#include + +// Markups Widgets includes +#include "qMRMLMarkupsOptionsWidgetsFactory.h" + +// Qt includes +#include + +//----------------------------------------------------------------------------- +/// \ingroup Slicer_QtModules_ExtensionTemplate +class qSlicerGUIWidgetsModulePrivate +{ +public: + qSlicerGUIWidgetsModulePrivate(); +}; + +//----------------------------------------------------------------------------- +// qSlicerGUIWidgetsModulePrivate methods + +//----------------------------------------------------------------------------- +qSlicerGUIWidgetsModulePrivate::qSlicerGUIWidgetsModulePrivate() +{ +} + +//----------------------------------------------------------------------------- +// qSlicerGUIWidgetsModule methods + +//----------------------------------------------------------------------------- +qSlicerGUIWidgetsModule::qSlicerGUIWidgetsModule(QObject* _parent) + : Superclass(_parent) + , d_ptr(new qSlicerGUIWidgetsModulePrivate) +{ +} + +//----------------------------------------------------------------------------- +qSlicerGUIWidgetsModule::~qSlicerGUIWidgetsModule() +{ +} + +//----------------------------------------------------------------------------- +QString qSlicerGUIWidgetsModule::helpText() const +{ + return "This is a loadable module that can be bundled in an extension"; +} + +//----------------------------------------------------------------------------- +QString qSlicerGUIWidgetsModule::acknowledgementText() const +{ + return "This work was partially funded by the grant 'ICEX Espana Exportacion e Inversiones' under\ + the program 'Inversiones de Empresas Extranjeras en Actividades de I+D\ + (Fondo Tecnologico)- Convocatoria 2021'"; +} + +//----------------------------------------------------------------------------- +QStringList qSlicerGUIWidgetsModule::contributors() const +{ + QStringList moduleContributors; + moduleContributors << QString("Csaba Pinter (Ebatinca)"); + return moduleContributors; +} + +//----------------------------------------------------------------------------- +QIcon qSlicerGUIWidgetsModule::icon() const +{ + return QIcon(":/Icons/GUIWidgets.png"); +} + +//----------------------------------------------------------------------------- +QStringList qSlicerGUIWidgetsModule::categories() const +{ + return QStringList() << "Virtual Reality"; +} + +//----------------------------------------------------------------------------- +QStringList qSlicerGUIWidgetsModule::dependencies() const +{ + return QStringList() << "Markups"; +} + +//----------------------------------------------------------------------------- +void qSlicerGUIWidgetsModule::setup() +{ + this->Superclass::setup(); + + // Register displayable managers (same displayable manager handles both slice and 3D views) + //TODO: We do not register it because since the MRML node is a subclass of the plane markup, the markups DM takes over + //vtkMRMLThreeDViewDisplayableManagerFactory::GetInstance()->RegisterDisplayableManager("vtkMRMLGUIWidgetsDisplayableManager"); + + // Register markups + vtkSlicerApplicationLogic* appLogic = this->appLogic(); + if (!appLogic) + { + qCritical() << Q_FUNC_INFO << " : invalid application logic."; + return; + } + vtkSlicerMarkupsLogic* markupsLogic = vtkSlicerMarkupsLogic::SafeDownCast(appLogic->GetModuleLogic("Markups")); + if (!markupsLogic) + { + qCritical() << Q_FUNC_INFO << " : invalid markups logic."; + return; + } + vtkNew guiWidgetNode; + vtkNew vtkQWidgetWidget; + markupsLogic->RegisterMarkupsNode(guiWidgetNode, vtkQWidgetWidget); + + // Create and configure the additional widgets + //auto optionsWidgetFactory = qSlicerMarkupsAdditionalOptionsWidgetsFactory::instance(); + //optionsWidgetFactory->registerAdditionalOptionsWidget(new qSlicerMarkupsGUIWidget()); +} + +//----------------------------------------------------------------------------- +qSlicerAbstractModuleRepresentation* qSlicerGUIWidgetsModule::createWidgetRepresentation() +{ + return new qSlicerGUIWidgetsModuleWidget; +} + +//----------------------------------------------------------------------------- +vtkMRMLAbstractLogic* qSlicerGUIWidgetsModule::createLogic() +{ + return vtkSlicerGUIWidgetsLogic::New(); +} diff --git a/GUIWidgets/qSlicerGUIWidgetsModule.h b/GUIWidgets/qSlicerGUIWidgetsModule.h new file mode 100644 index 0000000..89edc63 --- /dev/null +++ b/GUIWidgets/qSlicerGUIWidgetsModule.h @@ -0,0 +1,77 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +#ifndef __qSlicerGUIWidgetsModule_h +#define __qSlicerGUIWidgetsModule_h + +// Slicer includes +#include "qSlicerLoadableModule.h" + +#include "qSlicerGUIWidgetsModuleExport.h" + +class qSlicerGUIWidgetsModulePrivate; + +/// \ingroup Slicer_QtModules_ExtensionTemplate +class Q_SLICER_QTMODULES_GUIWIDGETS_EXPORT qSlicerGUIWidgetsModule : public qSlicerLoadableModule +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.slicer.modules.loadable.qSlicerLoadableModule/1.0"); + Q_INTERFACES(qSlicerLoadableModule); + +public: + + typedef qSlicerLoadableModule Superclass; + explicit qSlicerGUIWidgetsModule(QObject *parent=0); + virtual ~qSlicerGUIWidgetsModule(); + + qSlicerGetTitleMacro(QTMODULE_TITLE); + + virtual QString helpText()const; + virtual QString acknowledgementText()const; + virtual QStringList contributors()const; + + virtual QIcon icon()const; + + virtual QStringList categories()const; + virtual QStringList dependencies() const; + +protected: + + /// Initialize the module. Register the volumes reader/writer + virtual void setup(); + + /// Create and return the widget representation associated to this module + virtual qSlicerAbstractModuleRepresentation* createWidgetRepresentation(); + + /// Create and return the logic associated to this module + virtual vtkMRMLAbstractLogic* createLogic(); + +protected: + QScopedPointer d_ptr; + +private: + Q_DECLARE_PRIVATE(qSlicerGUIWidgetsModule); + Q_DISABLE_COPY(qSlicerGUIWidgetsModule); + +}; + +#endif diff --git a/GUIWidgets/qSlicerGUIWidgetsModuleWidget.cxx b/GUIWidgets/qSlicerGUIWidgetsModuleWidget.cxx new file mode 100644 index 0000000..c07a6ca --- /dev/null +++ b/GUIWidgets/qSlicerGUIWidgetsModuleWidget.cxx @@ -0,0 +1,403 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +// GUI Widgets includes +#include "qSlicerGUIWidgetsModuleWidget.h" +#include "ui_qSlicerGUIWidgetsModuleWidget.h" + +#include "vtkMRMLGUIWidgetNode.h" +#include "vtkMRMLGUIWidgetDisplayNode.h" + +#include "vtkSlicerQWidgetWidget.h" +#include "vtkSlicerQWidgetRepresentation.h" + +#include "vtkSlicerQWidgetTexture.h" + +// VirtualReality includes +#include "vtkMRMLVirtualRealityViewNode.h" +#include "vtkSlicerVirtualRealityLogic.h" + +// Slicer includes +#include "qSlicerApplication.h" +#include "qSlicerLayoutManager.h" + +#include "vtkSlicerApplicationLogic.h" + +// qMRMLWidget includes +#include "qMRMLThreeDView.h" +#include "qMRMLThreeDWidget.h" + +// Markups Logic includes +#include + +// Virtual Reality includes +#include "qMRMLVirtualRealityHomeWidget.h" +#include "qMRMLVirtualRealityDataModuleWidget.h" +#include "qMRMLVirtualRealitySegmentEditorWidget.h" +#include "qMRMLVirtualRealityTransformWidget.h" + +// MRML includes +#include "vtkMRMLLinearTransformNode.h" +#include "vtkMRMLScene.h" +#include "vtkMRMLViewNode.h" + +#include "vtkMRMLMarkupsDisplayableManager.h" +#include "vtkMRMLCameraDisplayableManager.h" +#include "vtkMRMLMarkupsDisplayableManagerHelper.h" +#include "vtkMRMLVirtualRealityViewDisplayableManagerFactory.h" + +// VTK includes +#include "vtkRenderer.h" +#include "vtkMatrix4x4.h" +#include "vtkPlaneSource.h" +#include "vtkPolyData.h" +#include "vtkPolyDataMapper.h" +#include "vtkDataSet.h" +#include "vtkCellLocator.h" + +// Qt includes +#include +#include +#include +#include +#include +#include +#include +#include + +//----------------------------------------------------------------------------- +/// \ingroup Slicer_QtModules_ExtensionTemplate +class qSlicerGUIWidgetsModuleWidgetPrivate: public Ui_qSlicerGUIWidgetsModuleWidget +{ +public: + qSlicerGUIWidgetsModuleWidgetPrivate(); +}; + +//----------------------------------------------------------------------------- +// qSlicerGUIWidgetsModuleWidgetPrivate methods + +//----------------------------------------------------------------------------- +qSlicerGUIWidgetsModuleWidgetPrivate::qSlicerGUIWidgetsModuleWidgetPrivate() +{ +} + + +//----------------------------------------------------------------------------- +// qSlicerGUIWidgetsModuleWidget methods + +//----------------------------------------------------------------------------- +qSlicerGUIWidgetsModuleWidget::qSlicerGUIWidgetsModuleWidget(QWidget* _parent) + : Superclass( _parent ) + , d_ptr( new qSlicerGUIWidgetsModuleWidgetPrivate ) +{ +} + +//----------------------------------------------------------------------------- +qSlicerGUIWidgetsModuleWidget::~qSlicerGUIWidgetsModuleWidget() +{ + this->GUIWidgetsMap.clear(); +} + +//----------------------------------------------------------------------------- +void qSlicerGUIWidgetsModuleWidget::setup() +{ + Q_D(qSlicerGUIWidgetsModuleWidget); + d->setupUi(this); + this->Superclass::setup(); + + QObject::connect(d->AddHelloWorldGUIWidgetNodeButton, SIGNAL(clicked()), this, SLOT(onAddHelloWorldNodeClicked())); + QObject::connect(d->UpdateButtonLabelButton, SIGNAL(clicked()), this, SLOT(onUpdateButtonLabelButtonClicked())); + + QObject::connect(d->AddHomeWidgetButton, SIGNAL(clicked()), this, SLOT(onAddHomeWidgetButtonClicked())); + QObject::connect(d->AddDataModuleWidgetButton, SIGNAL(clicked()), this, SLOT(onAddDataModuleWidgetButtonClicked())); + QObject::connect(d->AddSegmentEditorWidgetButton, SIGNAL(clicked()), this, SLOT(onAddSegmentEditorWidgetButtonClicked())); + QObject::connect(d->AddTransformWidgetButton, SIGNAL(clicked()), this, SLOT(onAddTransformWidgetButtonClicked())); + + QObject::connect(d->SetUpInteractionButton, SIGNAL(clicked()), this, SLOT(onSetUpInteractionButtonClicked())); + QObject::connect(d->StartInteractionButton, SIGNAL(clicked()), this, SLOT(onStartInteractionButtonClicked())); +} + +//----------------------------------------------------------------------------- +void qSlicerGUIWidgetsModuleWidget::setWidgetToGUIWidgetMarkupsNode(vtkMRMLGUIWidgetNode* node, QWidget* widget) +{ + if (!node) + { + return; + } + + node->SetWidget((void*)widget); + + this->GUIWidgetsMap[node] = widget; +} + +//----------------------------------------------------------------------------- +QWidget* qSlicerGUIWidgetsModuleWidget::onAddHelloWorldNodeClicked() +{ + qSlicerApplication* app = qSlicerApplication::application(); + vtkMRMLGUIWidgetNode* widgetNode = vtkMRMLGUIWidgetNode::SafeDownCast( + app->mrmlScene()->AddNewNodeByClass("vtkMRMLGUIWidgetNode") ); + widgetNode->SetName("TestButtonWidgetNode"); + + QPushButton* newButton = new QPushButton("Hello world!"); + this->setWidgetToGUIWidgetMarkupsNode(widgetNode, newButton); + + return newButton; +} + +//----------------------------------------------------------------------------- +void qSlicerGUIWidgetsModuleWidget::onUpdateButtonLabelButtonClicked() +{ + Q_D(qSlicerGUIWidgetsModuleWidget); + + // Get last widget + QWidget* lastWidget = this->GUIWidgetsMap.last(); + + QPushButton* button = qobject_cast(lastWidget); + if (button) + { + button->setText(d->NewLabelLineEdit->text()); + } + else + { + qCritical() << Q_FUNC_INFO << ": Widget is not a push button"; + } +} + +//----------------------------------------------------------------------------- +void qSlicerGUIWidgetsModuleWidget::onAddHomeWidgetButtonClicked() +{ + Q_D(qSlicerGUIWidgetsModuleWidget); + + qSlicerApplication* app = qSlicerApplication::application(); + vtkMRMLGUIWidgetNode* widgetNode = vtkMRMLGUIWidgetNode::SafeDownCast( + app->mrmlScene()->AddNewNodeByClass("vtkMRMLGUIWidgetNode") ); + widgetNode->SetName("HomeWidgetNode"); + + qMRMLVirtualRealityHomeWidget* widget = new qMRMLVirtualRealityHomeWidget(); + widget->setMRMLScene(app->mrmlScene()); + this->setWidgetToGUIWidgetMarkupsNode(widgetNode, widget); +} + +//----------------------------------------------------------------------------- +void qSlicerGUIWidgetsModuleWidget::onAddDataModuleWidgetButtonClicked() +{ + Q_D(qSlicerGUIWidgetsModuleWidget); + + qSlicerApplication* app = qSlicerApplication::application(); + vtkMRMLGUIWidgetNode* widgetNode = vtkMRMLGUIWidgetNode::SafeDownCast( + app->mrmlScene()->AddNewNodeByClass("vtkMRMLGUIWidgetNode") ); + widgetNode->SetName("DataModuleWidgetNode"); + + qMRMLVirtualRealityDataModuleWidget* widget = new qMRMLVirtualRealityDataModuleWidget(); + widget->setMRMLScene(app->mrmlScene()); + this->setWidgetToGUIWidgetMarkupsNode(widgetNode, widget); +} + +//----------------------------------------------------------------------------- +void qSlicerGUIWidgetsModuleWidget::onAddSegmentEditorWidgetButtonClicked() +{ + Q_D(qSlicerGUIWidgetsModuleWidget); + + qSlicerApplication* app = qSlicerApplication::application(); + vtkMRMLGUIWidgetNode* widgetNode = vtkMRMLGUIWidgetNode::SafeDownCast( + app->mrmlScene()->AddNewNodeByClass("vtkMRMLGUIWidgetNode") ); + widgetNode->SetName("SegmentEditorWidgetNode"); + + qMRMLVirtualRealitySegmentEditorWidget* widget = new qMRMLVirtualRealitySegmentEditorWidget(); + widget->setMRMLScene(app->mrmlScene()); + this->setWidgetToGUIWidgetMarkupsNode(widgetNode, widget); +} + +//----------------------------------------------------------------------------- +void qSlicerGUIWidgetsModuleWidget::onAddTransformWidgetButtonClicked() +{ + Q_D(qSlicerGUIWidgetsModuleWidget); + + qSlicerApplication* app = qSlicerApplication::application(); + vtkMRMLGUIWidgetNode* widgetNode = vtkMRMLGUIWidgetNode::SafeDownCast( + app->mrmlScene()->AddNewNodeByClass("vtkMRMLGUIWidgetNode") ); + widgetNode->SetName("TransformWidgetNode"); + + vtkSlicerVirtualRealityLogic* vrLogic = + vtkSlicerVirtualRealityLogic::SafeDownCast(app->applicationLogic()->GetModuleLogic("VirtualReality")); + if (!vrLogic) + { + qCritical() << Q_FUNC_INFO << " : invalid VR logic"; + return; + } + + qMRMLVirtualRealityTransformWidget* widget = new qMRMLVirtualRealityTransformWidget(vrLogic->GetVirtualRealityViewNode()); + widget->setMRMLScene(app->mrmlScene()); + this->setWidgetToGUIWidgetMarkupsNode(widgetNode, widget); +} + +//----------------------------------------------------------------------------- +void qSlicerGUIWidgetsModuleWidget::onSetUpInteractionButtonClicked() +{ + std::cout << "----- onSetUpInteractionButtonClicked ----- \n"; +} + +//----------------------------------------------------------------------------- +void qSlicerGUIWidgetsModuleWidget::onStartInteractionButtonClicked() +{ + std::cout << "\n\nqSlicerGUIWidgetsModuleWidget::onStartInteractionButtonClicked() \n"; + + // Pointer transform + qSlicerApplication* app = qSlicerApplication::application(); + vtkMRMLLinearTransformNode* transformNode = vtkMRMLLinearTransformNode::SafeDownCast(app->mrmlScene()->GetFirstNodeByName("PointerTransform")); + if (!transformNode) + { + std::cout << "ERROR: Pointer transform was not found in scene... \n"; + return; + } + + // Define maximum distance for interaction + double maxDistanceForInteraction = 2000; // mm + + // Line points + double pointA_h[4] = { 0.0, 0.0, 0.0, 1.0 }; + double pointB_h[4] = { 0.0, 0.0, -maxDistanceForInteraction, 1.0 }; + + // Get transformed line points + double pointA_transf_h[4] = { 0.0, 0.0, 0.0, 1.0 }; + double pointB_transf_h[4] = { 0.0, 0.0, 0.0, 1.0 }; + vtkNew matrixTransformToWorld; + transformNode->GetMatrixTransformToWorld(matrixTransformToWorld); + matrixTransformToWorld->MultiplyPoint(pointA_h, pointA_transf_h); + matrixTransformToWorld->MultiplyPoint(pointB_h, pointB_transf_h); + double pointA_transf[3] = { pointA_transf_h[0], pointA_transf_h[1], pointA_transf_h[2] }; + double pointB_transf[3] = { pointB_transf_h[0], pointB_transf_h[1], pointB_transf_h[2] }; + + // Get GUI widget + vtkMRMLGUIWidgetNode* widgetNode = vtkMRMLGUIWidgetNode::SafeDownCast(app->mrmlScene()->GetFirstNodeByName("HomeWidgetNode")); + + // Get displayable manager + qSlicerLayoutManager* layoutManager = qSlicerApplication::application()->layoutManager(); + if (!layoutManager) + { + // application is closing + return; + } + qMRMLThreeDWidget* threeDWidget = layoutManager->threeDWidget(0); + vtkMRMLMarkupsDisplayableManager* markupsDisplayableManager = vtkMRMLMarkupsDisplayableManager::SafeDownCast(threeDWidget->threeDView()->displayableManagerByClassName("vtkMRMLMarkupsDisplayableManager")); + + // Get widget representation from displayabale manager + vtkMRMLMarkupsDisplayableManagerHelper* helper = markupsDisplayableManager->GetHelper(); + vtkSlicerQWidgetWidget* widget = vtkSlicerQWidgetWidget::SafeDownCast(helper->GetWidget(widgetNode->GetMarkupsDisplayNode())); + vtkSlicerQWidgetRepresentation* rep = vtkSlicerQWidgetRepresentation::SafeDownCast(widget->GetRepresentation()); + + // Get plane source + vtkPlaneSource* planeSource = vtkPlaneSource::SafeDownCast(rep->GetPlaneSource()); + + // Get plane normal + double* planeNormal = planeSource->GetNormal(); + std::cout << "Plane normal: [" << planeNormal[0] << ", " << planeNormal[1] << ", " << planeNormal[2] << "] \n"; + + // Get plane reference points + double* planePointSW = planeSource->GetOrigin(); // bottom left corner + double* planePointSE = planeSource->GetPoint1(); // bottom right corner + double* planePointNW = planeSource->GetPoint2(); // top left corner + double translationWtoE[3] = {0.0, 0.0, 0.0}; + vtkMath::Subtract(planePointSE, planePointSW, translationWtoE); + double planePointNE[3] = { 0.0, 0.0, 0.0 }; + vtkMath::Add(planePointNW, translationWtoE, planePointNE); + std::cout << "Plane point NW: [" << planePointNW[0] << ", " << planePointNW[1] << ", " << planePointNW[2] << "] \n"; + std::cout << "Plane point NE: [" << planePointNE[0] << ", " << planePointNE[1] << ", " << planePointNE[2] << "] \n"; + std::cout << "Plane point SW: [" << planePointSW[0] << ", " << planePointSW[1] << ", " << planePointSW[2] << "] \n"; + std::cout << "Plane point SE: [" << planePointSE[0] << ", " << planePointSE[1] << ", " << planePointSE[2] << "] \n"; + + // Compute intersection point + vtkNew cellLocator; + cellLocator->SetDataSet(planeSource->GetOutput()); + cellLocator->BuildLocator(); + double tolerance = 0.001; + double t = 0.0; + double pcoords[3] = { 0.0 }; + int subId = 0; + vtkIdType cellId = 0; + vtkNew cell; + double intersectionPoint[3]= { 0.0, 0.0, 0.0 }; + int foundIntersection = cellLocator->IntersectWithLine(pointA_transf, pointB_transf, tolerance, t, intersectionPoint, pcoords, subId, cellId, cell); + if (foundIntersection) + { + std::cout << "Intersection point: [" << intersectionPoint[0] << ", " << intersectionPoint[1] << ", " << intersectionPoint[2] << "] \n"; + } + else + { + std::cout << "No intersection was found... \n"; + return; + } + + // Get plane dimensions + vtkSlicerQWidgetTexture* texture = rep->GetQWidgetTexture(); + QWidget* qWidget = texture->GetWidget(); + if (!qWidget) + { + return; + } + QRect rect = qWidget->geometry(); + if (rect.width() < 2 || rect.height() < 2) + { + return; + } + std::cout << "Widget dimensions: width = " << rect.width() << " and height = " << rect.height() << "\n"; + double spacingMmPerPixel = rep->GetSpacingMmPerPixel(); + double bounds[6] = { + -(double)(rect.width() / 2) * spacingMmPerPixel, (double)rect.width() / 2 * spacingMmPerPixel, + -0.5, 0.5, + -(double)(rect.height() / 2) * spacingMmPerPixel, (double)rect.height() / 2 * spacingMmPerPixel + }; + std::cout << "Widget bounds: [ " << bounds[0] << ", " << bounds[1] << ", " << bounds[2] << ", " << bounds[3] << ", " << bounds[4] << ", " << bounds[5] << "\n"; + + // Compute pixel position + double intersectionPointVector[3] = { intersectionPoint[0] - planePointNW[0], intersectionPoint[1] - planePointNW[1], intersectionPoint[2] - planePointNW[2] }; + double xPlaneAxis[3] = { planePointNE[0] - planePointNW[0], planePointNE[1] - planePointNW[1], planePointNE[2] - planePointNW[2] }; + double yPlaneAxis[3] = { planePointSW[0] - planePointNW[0], planePointSW[1] - planePointNW[1], planePointSW[2] - planePointNW[2] }; + vtkMath::MultiplyScalar(xPlaneAxis, vtkMath::Dot(intersectionPointVector, xPlaneAxis) / vtkMath::Dot(xPlaneAxis, xPlaneAxis)); + vtkMath::MultiplyScalar(yPlaneAxis, vtkMath::Dot(intersectionPointVector, yPlaneAxis) / vtkMath::Dot(yPlaneAxis, yPlaneAxis)); + double xIntersectionPoint[3] = { 0.0, 0.0, 0.0 }; + double yIntersectionPoint[3] = { 0.0, 0.0, 0.0 }; + vtkMath::Add(planePointNW, xPlaneAxis, xIntersectionPoint); + vtkMath::Add(planePointNW, yPlaneAxis, yIntersectionPoint); + vtkMath::Subtract(xIntersectionPoint, planePointNW, xIntersectionPoint); // subtract plane origin + vtkMath::Subtract(yIntersectionPoint, planePointNW, yIntersectionPoint); // subtract plane origin + double xPositionMm = vtkMath::Norm(xIntersectionPoint); + double yPositionMm = vtkMath::Norm(yIntersectionPoint); + std::cout << "Pointer intersection position (mm): [ " << xPositionMm << ", " << yPositionMm << "] \n"; + int xPositionPixels = xPositionMm / spacingMmPerPixel; + int yPositionPixels = yPositionMm / spacingMmPerPixel; + std::cout << "Pointer intersection position (pixels): [ " << xPositionPixels << ", " << yPositionPixels << "] \n"; + + // Send press event + QGraphicsSceneMouseEvent pressEvent(QEvent::GraphicsSceneMousePress); + pressEvent.setScenePos(QPointF(xPositionPixels, yPositionPixels)); + pressEvent.setButton(Qt::LeftButton); + pressEvent.setButtons(Qt::LeftButton); + QApplication::sendEvent(texture->GetScene(), &pressEvent); + + // Send release event + QGraphicsSceneMouseEvent releaseEvent(QEvent::GraphicsSceneMouseRelease); + releaseEvent.setScenePos(QPointF(xPositionPixels, yPositionPixels)); + releaseEvent.setButton(Qt::LeftButton); + releaseEvent.setButtons(Qt::LeftButton); + QApplication::sendEvent(texture->GetScene(), &releaseEvent); +} diff --git a/GUIWidgets/qSlicerGUIWidgetsModuleWidget.h b/GUIWidgets/qSlicerGUIWidgetsModuleWidget.h new file mode 100644 index 0000000..e58bd69 --- /dev/null +++ b/GUIWidgets/qSlicerGUIWidgetsModuleWidget.h @@ -0,0 +1,76 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, EBATINCA, S.L., and + development was supported by "ICEX Espana Exportacion e Inversiones" under + the program "Inversiones de Empresas Extranjeras en Actividades de I+D + (Fondo Tecnologico)- Convocatoria 2021" + +==============================================================================*/ + +#ifndef __qSlicerGUIWidgetsModuleWidget_h +#define __qSlicerGUIWidgetsModuleWidget_h + +// Slicer includes +#include "qSlicerAbstractModuleWidget.h" + +#include "qSlicerGUIWidgetsModuleExport.h" + +// Qt includes +#include + +class qSlicerGUIWidgetsModuleWidgetPrivate; +class vtkMRMLGUIWidgetNode; + +/// \ingroup Slicer_QtModules_ExtensionTemplate +class Q_SLICER_QTMODULES_GUIWIDGETS_EXPORT qSlicerGUIWidgetsModuleWidget : + public qSlicerAbstractModuleWidget +{ + Q_OBJECT + +public: + + typedef qSlicerAbstractModuleWidget Superclass; + qSlicerGUIWidgetsModuleWidget(QWidget *parent=0); + virtual ~qSlicerGUIWidgetsModuleWidget(); + +public slots: + QWidget* onAddHelloWorldNodeClicked(); + void onUpdateButtonLabelButtonClicked(); + + void onAddHomeWidgetButtonClicked(); + void onAddDataModuleWidgetButtonClicked(); + void onAddSegmentEditorWidgetButtonClicked(); + void onAddTransformWidgetButtonClicked(); + void onSetUpInteractionButtonClicked(); + void onStartInteractionButtonClicked(); + + /// Assign widget to a GUIWidget markups node + void setWidgetToGUIWidgetMarkupsNode(vtkMRMLGUIWidgetNode* node, QWidget* widget); + +protected: + QScopedPointer d_ptr; + + virtual void setup(); + +protected: + QMap GUIWidgetsMap; + +private: + Q_DECLARE_PRIVATE(qSlicerGUIWidgetsModuleWidget); + Q_DISABLE_COPY(qSlicerGUIWidgetsModuleWidget); +}; + +#endif diff --git a/PointerSimulator/CMakeLists.txt b/PointerSimulator/CMakeLists.txt new file mode 100644 index 0000000..36081e9 --- /dev/null +++ b/PointerSimulator/CMakeLists.txt @@ -0,0 +1,31 @@ +#----------------------------------------------------------------------------- +set(MODULE_NAME PointerSimulator) + +#----------------------------------------------------------------------------- +set(MODULE_PYTHON_SCRIPTS + ${MODULE_NAME}.py + ) + +set(MODULE_PYTHON_RESOURCES + Resources/Icons/${MODULE_NAME}.png + Resources/UI/${MODULE_NAME}.ui + ) + +#----------------------------------------------------------------------------- +slicerMacroBuildScriptedModule( + NAME ${MODULE_NAME} + SCRIPTS ${MODULE_PYTHON_SCRIPTS} + RESOURCES ${MODULE_PYTHON_RESOURCES} + WITH_GENERIC_TESTS + ) + +#----------------------------------------------------------------------------- +if(BUILD_TESTING) + + # Register the unittest subclass in the main script as a ctest. + # Note that the test will also be available at runtime. + slicer_add_python_unittest(SCRIPT ${MODULE_NAME}.py) + + # Additional build-time testing + add_subdirectory(Testing) +endif() diff --git a/PointerSimulator/PointerSimulator.py b/PointerSimulator/PointerSimulator.py new file mode 100644 index 0000000..a272176 --- /dev/null +++ b/PointerSimulator/PointerSimulator.py @@ -0,0 +1,367 @@ +import os +import unittest +import logging +import vtk, qt, ctk, slicer +from slicer.ScriptedLoadableModule import * +from slicer.util import VTKObservationMixin +import numpy as np +import time + +#------------------------------------------------------------------------------ +# +# PointerSimulator +# +#------------------------------------------------------------------------------ +class PointerSimulator(ScriptedLoadableModule): + + def __init__(self, parent): + ScriptedLoadableModule.__init__(self, parent) + self.parent.title = "PointerSimulator" + self.parent.categories = ["Virtual Reality"] + self.parent.dependencies = [] + self.parent.contributors = ["David Garcia Mato (Ebatinca, S.L.)"] + self.parent.helpText = """ """ + self.parent.acknowledgementText = """ """ + +#------------------------------------------------------------------------------ +# +# PointerSimulatorWidget +# +#------------------------------------------------------------------------------ +class PointerSimulatorWidget(ScriptedLoadableModuleWidget, VTKObservationMixin): + + def __init__(self, parent): + """ + Called when the user opens the module the first time and the widget is initialized. + """ + ScriptedLoadableModuleWidget.__init__(self, parent) + VTKObservationMixin.__init__(self) + + self.logic = PointerSimulatorLogic(self) + self._parameterNode = None + self._updatingGUIFromParameterNode = False + + def setup(self): + """ + Called when the user opens the module the first time and the widget is initialized. + """ + ScriptedLoadableModuleWidget.setup(self) + + # Load widget from .ui file (created by Qt Designer). + # Additional widgets can be instantiated manually and added to self.layout. + uiWidget = slicer.util.loadUI(self.resourcePath('UI/PointerSimulator.ui')) + self.layout.addWidget(uiWidget) + self.ui = slicer.util.childWidgetVariables(uiWidget) + + # Set scene in MRML widgets. Make sure that in Qt designer the top-level qMRMLWidget's + # "mrmlSceneChanged(vtkMRMLScene*)" signal in is connected to each MRML widget's. + # "setMRMLScene(vtkMRMLScene*)" slot. + uiWidget.setMRMLScene(slicer.mrmlScene) + + # Connections + + # These connections ensure that we update parameter node when scene is closed + self.addObserver(slicer.mrmlScene, slicer.mrmlScene.StartCloseEvent, self.onSceneStartClose) + self.addObserver(slicer.mrmlScene, slicer.mrmlScene.EndCloseEvent, self.onSceneEndClose) + + # These connections ensure that whenever user changes some settings on the GUI, that is saved in the MRML scene + # (in the selected parameter node). + self.ui.setUpPointerButton.clicked.connect(self.onSetUpPointerButtonClicked) + + # Make sure parameter node is initialized (needed for module reload) + self.initializeParameterNode() + + def cleanup(self): + """ + Called when the application closes and the module widget is destroyed. + """ + self.removeObservers() + + def enter(self): + """ + Called each time the user opens this module. + """ + # Make sure parameter node exists and observed + self.initializeParameterNode() + + def exit(self): + """ + Called each time the user opens a different module. + """ + # Do not react to parameter node changes (GUI wlil be updated when the user enters into the module) + self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode) + + def onSceneStartClose(self, caller, event): + """ + Called just before the scene is closed. + """ + # Parameter node will be reset, do not use it anymore + self.setParameterNode(None) + + def onSceneEndClose(self, caller, event): + """ + Called just after the scene is closed. + """ + # If this module is shown while the scene is closed then recreate a new parameter node immediately + if self.parent.isEntered: + self.initializeParameterNode() + + def initializeParameterNode(self): + """ + Ensure parameter node exists and observed. + """ + # Parameter node stores all user choices in parameter values, node selections, etc. + # so that when the scene is saved and reloaded, these settings are restored. + + self.setParameterNode(self.logic.getParameterNode()) + + # Select default input nodes if nothing is selected yet to save a few clicks for the user + if not self._parameterNode.GetNodeReference("InputVolume"): + firstVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode") + if firstVolumeNode: + self._parameterNode.SetNodeReferenceID("InputVolume", firstVolumeNode.GetID()) + + def setParameterNode(self, inputParameterNode): + """ + Set and observe parameter node. + Observation is needed because when the parameter node is changed then the GUI must be updated immediately. + """ + + if inputParameterNode: + self.logic.setDefaultParameters(inputParameterNode) + + # Unobserve previously selected parameter node and add an observer to the newly selected. + # Changes of parameter node are observed so that whenever parameters are changed by a script or any other module + # those are reflected immediately in the GUI. + if self._parameterNode is not None: + self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode) + self._parameterNode = inputParameterNode + if self._parameterNode is not None: + self.addObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode) + + # Initial GUI update + self.updateGUIFromParameterNode() + + def updateGUIFromParameterNode(self, caller=None, event=None): + """ + This method is called whenever parameter node is changed. + The module GUI is updated to show the current state of the parameter node. + """ + + if self._parameterNode is None or self._updatingGUIFromParameterNode: + return + + # Make sure GUI changes do not call updateParameterNodeFromGUI (it could cause infinite loop) + self._updatingGUIFromParameterNode = True + + # All the GUI updates are done + self._updatingGUIFromParameterNode = False + + def updateParameterNodeFromGUI(self, caller=None, event=None): + """ + This method is called when the user makes any change in the GUI. + The changes are saved into the parameter node (so that they are restored when the scene is saved and loaded). + """ + + if self._parameterNode is None or self._updatingGUIFromParameterNode: + return + + wasModified = self._parameterNode.StartModify() # Modify all properties in a single batch + + self._parameterNode.EndModify(wasModified) + + def onSetUpPointerButtonClicked(self): + # Create pointer model + origin = [0.0, 0.0, 0.0] + direction = [0.0, 0.0, -1.0] + length = 500.0 + self.logic.updatePointerModel(origin, direction, length) + + # Create pointer transform + self.logic.updatePointerTransform(0.0,0.0,0.0) + + # Set pointer transform + self.ui.translationSliders.setMRMLTransformNode(self.logic.pointerTransform) + self.ui.rotationSliders.setMRMLTransformNode(self.logic.pointerTransform) + + # Transform pointer by controller transform + self.logic.applyControllerTransform() + +#------------------------------------------------------------------------------ +# +# PointerSimulatorLogic +# +#------------------------------------------------------------------------------ +class PointerSimulatorLogic(ScriptedLoadableModuleLogic, VTKObservationMixin): + + #------------------------------------------------------------------------------ + def __init__(self, widgetInstance, parent=None): + """ + Called when the logic class is instantiated. Can be used for initializing member variables. + """ + ScriptedLoadableModuleLogic.__init__(self, parent) + VTKObservationMixin.__init__(self) + + self.moduleWidget = widgetInstance + + # Pointer model + self.pointerModel = None + self.pointerLineSource = None + self.pointerRadius = 1.0 + + # Pointer transform + self.pointerTransform = None + + #------------------------------------------------------------------------------ + def setDefaultParameters(self, parameterNode): + """ + Initialize parameter node with default settings. + """ + if not parameterNode.GetParameter("TranslateRL"): + parameterNode.SetParameter("TranslateRL", "0.0") + if not parameterNode.GetParameter("TranslateAP"): + parameterNode.SetParameter("TranslateAP", "0.0") + if not parameterNode.GetParameter("TranslateSI"): + parameterNode.SetParameter("TranslateSI", "0.0") + + #------------------------------------------------------------------------------ + def updatePointerModel(self, origin, direction, length): + + # Get/create pointer model + if not self.pointerModel: + # Line source + self.pointerLineSource = vtk.vtkLineSource() + # Tube filter + tubeFilter = vtk.vtkTubeFilter() + tubeFilter.SetInputConnection(self.pointerLineSource.GetOutputPort()) + tubeFilter.SetRadius(self.pointerRadius) #Default is 0.5 + tubeFilter.SetNumberOfSides(50) + tubeFilter.Update() + # Create a mapper and actor + pointerMapper = vtk.vtkPolyDataMapper() + pointerMapper.SetInputConnection(tubeFilter.GetOutputPort()) + pointerActor = vtk.vtkActor() + pointerActor.SetMapper(pointerMapper) + # Create model node + self.pointerModel = slicer.vtkMRMLModelNode() + self.pointerModel.SetName('PointerModel') + self.pointerModel.SetPolyDataConnection(tubeFilter.GetOutputPort()) + slicer.mrmlScene.AddNode(self.pointerModel) + self.pointerModelDisplay = slicer.vtkMRMLModelDisplayNode() + self.pointerModelDisplay.SetSliceIntersectionVisibility(True) + self.pointerModelDisplay.SetColor(1,0,0) + self.pointerModelDisplay.SetOpacity(1.0) + slicer.mrmlScene.AddNode(self.pointerModelDisplay) + self.pointerModel.SetAndObserveDisplayNodeID(self.pointerModelDisplay.GetID()) + + # Disable toggle selectable property for model + self.pointerModel.SetSelectable(False) + + # Update pointer model properties + vtk.vtkMath().Normalize(direction) + startPoint = np.array(origin) + endPoint = startPoint + np.array(direction) * length + self.pointerLineSource.SetPoint1(startPoint) + self.pointerLineSource.SetPoint2(endPoint) + + # Apply scalars to simulate pointer fading away + self.createAndApplyColorTable(startPoint) + + #------------------------------------------------------------------------------ + def updatePointerTransform(self, translationRL, translationAP, translationSI): + + # Create pointer transform if it does not exist + if not self.pointerTransform: + self.pointerTransform=slicer.vtkMRMLLinearTransformNode() + self.pointerTransform.SetName("PointerTransform") + slicer.mrmlScene.AddNode(self.pointerTransform) + if self.pointerModel: + self.pointerModel.SetAndObserveTransformNodeID(self.pointerTransform.GetID()) + else: + logging.error('Pointer model was not found.') + + # Update transformation matrix + transform = vtk.vtkTransform() + transform.Translate(translationRL, translationAP, translationSI) + + # Set transform + self.pointerTransform.SetMatrixTransformToParent(transform.GetMatrix()) + + #------------------------------------------------------------------------------ + def createAndApplyColorTable(self, origin): + + # Get polydata + poly = self.pointerModel.GetPolyData() + + # Get number of points + numPoints = poly.GetNumberOfPoints() + + # Create scalar array + scalar_array = vtk.vtkFloatArray() + scalar_array.SetName("DistanceToOrigin") + scalar_array.SetNumberOfComponents(1) + + # Compute distance from each vertex to origin + normalizedDistanceToOrigin_array = list() + for i in range(numPoints): + point = poly.GetPoint(i) + distance = np.linalg.norm(np.array(point)-np.array(origin)) + normalizedDistanceToOrigin_array.append(distance) + + # Normalize distance from 0 to 1 + maxValue = max(normalizedDistanceToOrigin_array) + minValue = min(normalizedDistanceToOrigin_array) + for i in range(numPoints): + normalizedDistanceToOrigin_array[i] = (normalizedDistanceToOrigin_array[i] - minValue)/(maxValue - minValue) + normalizedDistanceToOrigin_array[i] = 1.0 - normalizedDistanceToOrigin_array[i] + + # Fill scalar array from list + scalar_array.SetNumberOfTuples(numPoints) + for j in range(numPoints): + scalar_array.SetTuple1(j,normalizedDistanceToOrigin_array[j]) + + # Add scalar to model + self.pointerModel.AddPointScalars(scalar_array) + + # Create custom color table + myColorTable = slicer.vtkMRMLColorTableNode() + myColorTable.SetTypeToUser() + myColorTable.SetNumberOfColors(256) + myColorTable.SetName("CustomColorTable") + for i in range(0,255): + myColorTable.SetColor(i, 1.0, 0.0, 0.0, (i+1e-16)/255.0) + + slicer.mrmlScene.AddNode(myColorTable) + + # Update visualization + self.pointerModel.GetDisplayNode().SetActiveScalarName(scalar_array.GetName()) + self.pointerModel.GetDisplayNode().SetScalarVisibility(True) + self.pointerModel.GetDisplayNode().SetAndObserveColorNodeID(myColorTable.GetID()) #apply color table + + #------------------------------------------------------------------------------ + def applyControllerTransform(self): + + # Get controller transform + try: + controllerTransform = slicer.util.getNode('VirtualReality.RightController') + except: + logging.error('ERROR: Right controller transform was not found in the scene.') + return + + # Apply transform + if self.pointerTransform: + self.pointerTransform.SetAndObserveTransformNodeID(controllerTransform.GetID()) + + + +#------------------------------------------------------------------------------ +# +# PointerSimulatorTest +# +#------------------------------------------------------------------------------ +class PointerSimulatorTest(ScriptedLoadableModuleTest): + + def runTest(self): + """Run as few or as many tests as needed here. + """ + pass \ No newline at end of file diff --git a/PointerSimulator/Resources/Icons/PointerSimulator.png b/PointerSimulator/Resources/Icons/PointerSimulator.png new file mode 100644 index 0000000000000000000000000000000000000000..5d83ab4f05067d6d5e30808fe07df6b4ac035349 GIT binary patch literal 21024 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

gM5Oyb7$7#KJA2&j@3Tx-)UlJ%O+Q7&{q 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 f>+Cwaj(z + + PointerSimulator + + + + 0 + 0 + 315 + 556 + + + + + + + Pointer Controller + + + + + + + + + Click + + + + + + Click + + + true + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + qMRMLTransformSliders + qMRMLWidget +

qMRMLTransformSliders.h
+ + + qMRMLWidget + QWidget +
qMRMLWidget.h
+ 1 +
+ + ctkCollapsibleButton + QWidget +
ctkCollapsibleButton.h
+ 1 +
+ + ctkCollapsibleGroupBox + QGroupBox +
ctkCollapsibleGroupBox.h
+ 1 +
+ + + + diff --git a/PointerSimulator/Testing/CMakeLists.txt b/PointerSimulator/Testing/CMakeLists.txt new file mode 100644 index 0000000..655007a --- /dev/null +++ b/PointerSimulator/Testing/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Python) diff --git a/PointerSimulator/Testing/Python/CMakeLists.txt b/PointerSimulator/Testing/Python/CMakeLists.txt new file mode 100644 index 0000000..5658d8b --- /dev/null +++ b/PointerSimulator/Testing/Python/CMakeLists.txt @@ -0,0 +1,2 @@ + +#slicer_add_python_unittest(SCRIPT ${MODULE_NAME}ModuleTest.py) diff --git a/PointerSimulator/__pycache__/PointerSimulator.cpython-36.pyc b/PointerSimulator/__pycache__/PointerSimulator.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..576494d57e3a5882e7e32edc89af78e9983ef233 GIT binary patch literal 9294 zcmcIq%X1q?dY{1zfB^`Q5J^$i!}jdUHg>&~WN$VVXV;cQN)Ejet%#I1i&VlIqMPJE zzzp0oAVq{q4)iTa)h4H0a@kW-l}qlaRQ`oja_U2>QaPklmBXI*lKj4}2ZM(|+n0bf zJ^h-V?(gyY9>Wjo_3GdL?|JaU4=wAztchO{^*gwdU!f3|u)9{v-0hal?Lw=-cc z-EFyiFSd$&FSSaz7rNzLdd}&3y-KTMn>DJf1)fuD)jqX^D~exQqA2U6-{(JZLak8i#yzlRPy|&X< zVLz4PVH}8HzbhZbV$hY^+uIAHFx}hJrGB7fl29#__dDp{;4Ws!ArQ3MHH4@vpu_-UBj!hE3A(W zyA2-VD8)LjyZxY9);H$Mt~rn45s_OLyYXSz){A?PWN;Sxxg86s-`pGY1*yd0r%$&Y zsJQodYPi~l@IBv2A@0qBBBrvi&T^at?OV8#8Vbv;LCi|@R~WuMpU}CmwQYI=KSEAY zW5$O6K*bQ8hzxQ&9n$=wu+Q=LlJKF_DFZh~qaM{k{>qu%sbc;DkJQeAAbWMpUZg&{ zI!kCZ&K;YuW!!rSt>vt@(pl-Ob}pl5jaI?^%DDwr7epD@en%bF&rdovWCGzkJ1Oo$EaB2CXPcKrQe8SvZDkPq$tNg+X`-((*5>{Nhel`SmAR zCA_me8vB>#n`#ZSbup3Ypsz~_W07_TY1q}JF5vR%maZw8!~@lqkAw6Gy}@ZbNOdI; z!bqWBZ6Af*tnn;RAwg`S3xiPCu)m$@MYDsdFacB)t7 zXoJ+iOyDYpuZ;C^T4$D7qhxjfm=f95Yg2u@Du|MUSoK=VDOF)5f%L$6Ayv{^&1jO( zVnuEAjaAKin?=?~?G3_=%q1A4!!Xhn5i0B(OM)3PI!upd_4PW)_(NQYkHT`R?lO$S zVzK6W#Wj1)U3NUD3LSRshSP9{H)eTZ-nY&&kt;AMGF_~|gaZ(Gixr0)jl%HpTr3Rw zeUO&V@3)DGNsfFOE}rB7C}fhV_%w$4099a}YUFHGp#t=Enf96+4D>QoLJc3laR`y_c%80jvk{U{lLnYqj3M?hmAGLobQXJQU^Mu?&pwxa$Z zeHM2IJ*lrifQ@Ymx`8sC1jXCUs=AAP8e`Oe+c+f)MgLMm?ZDx*FYl*$X@jhl*L1^-)xRNRgtK4vE0Cm?M zu3x~A3+&gF3!FpAK9&T*NzlL=<9VTu*zb8o-6A$nD|I_y5Nc)ie8?k%Q=H9uhMG5L z7R@IA%9x42Va9A2(T-L<7A&VBGV<)qZm{H^~TW*P)^OThG!W18nW#o;K?jG zkr@k&D2#ACIf_phl-dKu<|Uy>!;cTRbDDD*7j_?jj$~!jRTLTUUW`vf{a@iqD8{x* z4cm1}&hYjH6uj6j<+LP?$Z5Hb#ynb5AHmm4(hAZIXTs+E)7May&(1MoFL=)W&}k;J z2myHzgd7vsNHgFJK$ir^kZWXL;yDEL-%F@FK!AB9PkBBZcjPH1W7FK`nWZQSB`;r! z=12?NG4=HVJK1d9Pjv;*ZmLb!cwzTI9<~~K;Uq{#KEMoa>C79LQV5Dfams(h0%Oh< zs)euN+`Kh%Lqmo$H#Fo+q?s>(JE0T|PRAh=GODXh)&`33XsjsQ%p8#kVva_>C_-Px zGfERPip+D$P?fjUO}uYd**KEz=Tdy+@9J_EM?7d1xz_Y_v9G{7^&L!A@1ua*YUV3) z-J_X0>tUKhn-SxRM{C$g@j%j#9YD10lmCO1Kvh(|fp= zL`76_FN+26tES$;qf>&Dt2~X2Qxlx|ff0ldAaO~ez;z@lu#7X5tm?D*AI`+Usf5`l zJPaeCUWC*R5|Hai7DcdOlp_*8JW-5hSs$cmfWp0fK<@D{kKmgneSLmD$b`>j zY+l#;CX$3cA~L;{wH}6%+~Fu**R!rCfd~hQLXP`_D1|xxZx}W%hg)?x>*Ks0Tk-41 zNgtvn{pg;n*1(s?gbGP%dSNXfAOR|Mrt!;hHq zTO;DV<^!G}obZLQ;yx6MGGK`zXKR6DYy{=<$-|*@YyH+l&)vrpjh!uMg08_!%YjGN zb|zYx$p=LzvuYE<&T{?`2meo8S@x_7x6NF`@6W2Hp(FPDngh~i2OO)A_m{hE{|?Y2 z6ZA_vIZoN5CP_ujg>iDv;I3iR>69{0+eK0hP8x%l+o_mP3A3u?py8x|r)Fk~ z!Ycdd`4}bJ>e-l+k1gVD#j_%kZ*KlBYSB8Qcbj&7)W(VpJXJa?VUKVs%?{Cf3BBdB za%cIB!8S#*j(5f9>|D-48tJ=<=Tqa2xeUsTd>i7HZ*kXh{X+vis2J7&FP&Y7V))!#jN8R@X|m{>k^<(rBhu? zG7^&=`Vt36GFg!rnc4vJDT1w`e*!4$}>cP;AG< zqA*OCY12rI>&vK&))}AKL~dogmuHV+Lw4Oy|T)wl>wzh|UBxcT@AVNoohlFXHn8=OW$WXn1J-@@|h$5i|_idHqv<)Kw0 z(ni8aM{8s%3~gH@(_)BTW#;nxw8!it_lE8J*vQ|;3;qXJ@)-)tftfFOc7q`hb$c05 zxQw1f^fma7Z&>KBp}oj3Nplx5XW5}PB{J#n_2i0yPUGQEs!9%=Q(qunU^7rmE7^u3 z!;=SW8i2va9#&{TD1+xHMNv|lm``6N2nsv{3sE(#bgGoD00b;_@I8V-w|-{R_k!%} zfYn(9EH0TRSh=0;*61T@p6<=_e-us0t`CPA*^CrE!XOgs%?6ukBiz?A;U>)vRK5bq zr=t85%d+}{Y6J!NgfuB=Q}ainR_?}!NG%^Cv!zt540_b?o_w~haFwhkOD?2Pg)j2q z-54ekGNvoQ4l|Y|uv_(XoCAOIaHXhpP(d|x(#r+kU3}_ZCfHyA zF|}_53mi;sH@&&>%|pCE5ojqAI7M%s*v%N%C{FlSTz5e#z0tz>mjP-wFY7{# z|DhPbiKjiQFX?IaTMA#K+G(fH4c@(JzDlmok9_n(Hca0v6-67m7!7*(_lQ5Cx=UZ% zRf(1+((&g`jw#hMY7!fPXLJ8QgbvF)op&Vd{RBXb|04OASYtw12&8BGxadDIymF(l L{AbpOjg$Waal((X literal 0 HcmV?d00001 diff --git a/VirtualReality/Logic/CMakeLists.txt b/VirtualReality/Logic/CMakeLists.txt index 19c8b53..b08f0d2 100644 --- a/VirtualReality/Logic/CMakeLists.txt +++ b/VirtualReality/Logic/CMakeLists.txt @@ -4,7 +4,7 @@ set(KIT ${PROJECT_NAME}) set(${KIT}_EXPORT_DIRECTIVE "VTK_SLICER_${MODULE_NAME_UPPER}_MODULE_LOGIC_EXPORT") -set(${KIT}_INCLUDE_DIRECTORIES +set(${KIT}_INCLUDE_DIRS ) set(${KIT}_SRCS diff --git a/VirtualReality/MRML/CMakeLists.txt b/VirtualReality/MRML/CMakeLists.txt index 112612f..cafd9b6 100644 --- a/VirtualReality/MRML/CMakeLists.txt +++ b/VirtualReality/MRML/CMakeLists.txt @@ -5,7 +5,7 @@ set(KIT ${PROJECT_NAME}) #----------------------------------------------------------------------------- set(${KIT}_EXPORT_DIRECTIVE "VTK_SLICER_${MODULE_NAME_UPPER}_MODULE_MRML_EXPORT") -set(${KIT}_INCLUDE_DIRECTORIES +set(${KIT}_INCLUDE_DIRS ${VTK_INCLUDE_DIRS} ) diff --git a/VirtualReality/Resources/menuTextureImage2.png b/VirtualReality/Resources/menuTextureImage2.png new file mode 100644 index 0000000000000000000000000000000000000000..9cf74e2cfa2737c599cff638c9dd86b6ef1f9302 GIT binary patch literal 2026761 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 Xule*N(dbM6*SEj&7r*6=|M33@+)}uz literal 0 HcmV?d00001 diff --git a/VirtualReality/SubjectHierarchyPlugins/CMakeLists.txt b/VirtualReality/SubjectHierarchyPlugins/CMakeLists.txt index 7427d03..7c3896e 100644 --- a/VirtualReality/SubjectHierarchyPlugins/CMakeLists.txt +++ b/VirtualReality/SubjectHierarchyPlugins/CMakeLists.txt @@ -4,7 +4,7 @@ set(KIT ${PROJECT_NAME}) set(${KIT}_EXPORT_DIRECTIVE "Q_SLICER_${MODULE_NAME_UPPER}_SUBJECT_HIERARCHY_PLUGINS_EXPORT") -set(${KIT}_INCLUDE_DIRECTORIES +set(${KIT}_INCLUDE_DIRS ${qSlicerSubjectHierarchyModuleWidgets_INCLUDE_DIRS} ${vtkSlicerSubjectHierarchyModuleLogic_INCLUDE_DIRS} ${qMRMLWidgets_INCLUDE_DIRS} diff --git a/VirtualReality/Widgets/CMakeLists.txt b/VirtualReality/Widgets/CMakeLists.txt index da8dd9b..e7d59b8 100644 --- a/VirtualReality/Widgets/CMakeLists.txt +++ b/VirtualReality/Widgets/CMakeLists.txt @@ -5,9 +5,12 @@ set(KIT ${PROJECT_NAME}) #----------------------------------------------------------------------------- set(${KIT}_EXPORT_DIRECTIVE "Q_SLICER_QTMODULES_${MODULE_NAME_UPPER}_WIDGETS_EXPORT") -set(${KIT}_INCLUDE_DIRECTORIES +set(${KIT}_INCLUDE_DIRS ${vtkSlicerVirtualRealityModuleMRML_INCLUDE_DIRS} ${VTK_INCLUDE_DIRS} + ${qSlicerSubjectHierarchyModuleWidgets_INCLUDE_DIRS} + ${qSlicerSegmentationsModuleWidgets_INCLUDE_DIRS} + ${qSlicerSegmentationsEditorEffects_INCLUDE_DIRS} ) set(${KIT}_SRCS @@ -16,19 +19,32 @@ set(${KIT}_SRCS qMRML${MODULE_NAME}View.h qMRML${MODULE_NAME}TransformWidget.cxx qMRML${MODULE_NAME}TransformWidget.h + qMRMLVirtualRealityHomeWidget.cxx + qMRMLVirtualRealityHomeWidget.h + qMRMLVirtualRealityDataModuleWidget.cxx + qMRMLVirtualRealityDataModuleWidget.h + qMRMLVirtualRealitySegmentEditorWidget.cxx + qMRMLVirtualRealitySegmentEditorWidget.h ) set(${KIT}_MOC_SRCS qMRML${MODULE_NAME}View.h qMRML${MODULE_NAME}TransformWidget.h qMRML${MODULE_NAME}View_p.h + qMRMLVirtualRealityHomeWidget.h + qMRMLVirtualRealityDataModuleWidget.h + qMRMLVirtualRealitySegmentEditorWidget.h ) set(${KIT}_UI_SRCS + Resources/UI/qMRMLVirtualRealityHomeWidget.ui + Resources/UI/qMRMLVirtualRealityDataModuleWidget.ui + Resources/UI/qMRMLVirtualRealitySegmentEditorWidget.ui Resources/UI/qMRML${MODULE_NAME}TransformWidget.ui ) set(${KIT}_RESOURCES + Resources/${KIT}.qrc Resources/qMRML${MODULE_NAME}TransformWidget.qrc ) @@ -36,6 +52,9 @@ set(${KIT}_TARGET_LIBRARIES vtkSlicer${MODULE_NAME}ModuleMRML vtkSlicerCamerasModuleLogic ${VTK_LIBRARIES} + qSlicerSubjectHierarchyModuleWidgets + qSlicerSegmentationsModuleWidgets + ) #----------------------------------------------------------------------------- diff --git a/VirtualReality/Widgets/Resources/StyleSheets/VrWidgetStyle.qss b/VirtualReality/Widgets/Resources/StyleSheets/VrWidgetStyle.qss new file mode 100644 index 0000000..ee037b4 --- /dev/null +++ b/VirtualReality/Widgets/Resources/StyleSheets/VrWidgetStyle.qss @@ -0,0 +1,50 @@ +QWidget { + font: 20px; +} + +QAbstractButton { + color: #3a3a3a; + border-color: #3a3a3a; + border-style: solid; + border-width: 1px; + border-radius: 10px; + padding: 4px; +} + +QAbstractButton:hover { + background-color:#ABABAB; +} + +QAbstractButton:pressed { + background-color: #525252; +} + +QSlider { + min-height: 68px; + max-height: 68px; + background: white; +} + +QSlider::groove:horizontal { + border: 1px solid #262626; + height: 5px; + background: #399aef; + margin: 0 12px; +} + +QSlider::handle:horizontal { + background: white; + border: 2px solid black; + border-radius: 10px; + width: 23px; + height: 100px; + margin: -24px -12px; +} + +QSlider::handle:horizontal:hover { + background: #ABABAB; +} + +QSlider::handle:horizontal:pressed { + background: #525252; +} diff --git a/VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealityDataModuleWidget.ui b/VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealityDataModuleWidget.ui new file mode 100644 index 0000000..1872872 --- /dev/null +++ b/VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealityDataModuleWidget.ui @@ -0,0 +1,69 @@ + + + qMRMLVirtualRealityDataModuleWidget + + + + 0 + 0 + 413 + 256 + + + + VR Data + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + qMRMLWidget + QWidget +
qMRMLWidget.h
+ 1 +
+ + qMRMLSubjectHierarchyTreeView + QTreeView +
qMRMLSubjectHierarchyTreeView.h
+
+
+ + + + qMRMLVirtualRealityDataModuleWidget + mrmlSceneChanged(vtkMRMLScene*) + SubjectHierarchyTreeView + setMRMLScene(vtkMRMLScene*) + + + 300 + 2 + + + 301 + 26 + + + + +
diff --git a/VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealityHomeWidget.ui b/VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealityHomeWidget.ui new file mode 100644 index 0000000..e846504 --- /dev/null +++ b/VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealityHomeWidget.ui @@ -0,0 +1,208 @@ + + + qMRMLVirtualRealityHomeWidget + + + + 0 + 0 + 532 + 263 + + + + VR Home + + + + + + Back + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + Sync view to reference view + + + + + + + Fly speed: + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + 1 + + + 0.500000000000000 + + + 2.000000000000000 + + + 0.100000000000000 + + + 10.000000000000000 + + + 1.660000000000000 + + + m/s + + + + + + + Magnification: + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + + 10x + + + + + + + Lock magnification + + + + + + + 0.01x + + + + + + + 1x + + + + + + + 0.1x + + + + + + + 100x + + + + + + + + + + 0 + 13 + + + + Motion sensitivity: + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + 0 + + + 100.000000000000000 + + + 50.000000000000000 + + + % + + + + + + + + + + Modules + + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + 0 + + + + + + + + qMRMLWidget + QWidget +
qMRMLWidget.h
+ 1 +
+ + ctkSliderWidget + QWidget +
ctkSliderWidget.h
+
+
+ + +
diff --git a/VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealitySegmentEditorWidget.ui b/VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealitySegmentEditorWidget.ui new file mode 100644 index 0000000..9cf629d --- /dev/null +++ b/VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealitySegmentEditorWidget.ui @@ -0,0 +1,79 @@ + + + qMRMLVirtualRealitySegmentEditorWidget + + + + 0 + 0 + 589 + 590 + + + + + 100 + 0 + + + + qMRMLSegmentEditorWidget + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + qMRMLWidget + QWidget +
qMRMLWidget.h
+ 1 +
+ + qMRMLSegmentEditorWidget + qMRMLWidget +
qMRMLSegmentEditorWidget.h
+ 1 +
+
+ + + + + + + qMRMLVirtualRealitySegmentEditorWidget + mrmlSceneChanged(vtkMRMLScene*) + SegmentEditorWidget + setMRMLScene(vtkMRMLScene*) + + + 189 + 581 + + + 189 + 533 + + + + +
diff --git a/VirtualReality/Widgets/Resources/qSlicerVirtualRealityModuleWidgets.qrc b/VirtualReality/Widgets/Resources/qSlicerVirtualRealityModuleWidgets.qrc new file mode 100644 index 0000000..9cb40ca --- /dev/null +++ b/VirtualReality/Widgets/Resources/qSlicerVirtualRealityModuleWidgets.qrc @@ -0,0 +1,5 @@ + + + StyleSheets/VrWidgetStyle.qss + + diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityDataModuleWidget.cxx b/VirtualReality/Widgets/qMRMLVirtualRealityDataModuleWidget.cxx new file mode 100644 index 0000000..9a41f32 --- /dev/null +++ b/VirtualReality/Widgets/qMRMLVirtualRealityDataModuleWidget.cxx @@ -0,0 +1,90 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through the Applied Cancer Research Unit program of Cancer Care + Ontario with funds provided by the Ontario Ministry of Health and Long-Term Care + and CANARIE. + +==============================================================================*/ + +// VirtualReality Widgets includes +#include "qMRMLVirtualRealityDataModuleWidget.h" +#include "ui_qMRMLVirtualRealityDataModuleWidget.h" + +// VirtualReality MRML includes +#include "vtkMRMLVirtualRealityViewNode.h" + +// Qt includes +#include + +//----------------------------------------------------------------------------- +class qMRMLVirtualRealityDataModuleWidgetPrivate : public Ui_qMRMLVirtualRealityDataModuleWidget +{ + Q_DECLARE_PUBLIC(qMRMLVirtualRealityDataModuleWidget); + +protected: + qMRMLVirtualRealityDataModuleWidget* const q_ptr; + +public: + qMRMLVirtualRealityDataModuleWidgetPrivate(qMRMLVirtualRealityDataModuleWidget& object); + virtual ~qMRMLVirtualRealityDataModuleWidgetPrivate(); + + void init(); +}; + +//----------------------------------------------------------------------------- +qMRMLVirtualRealityDataModuleWidgetPrivate::qMRMLVirtualRealityDataModuleWidgetPrivate(qMRMLVirtualRealityDataModuleWidget& object) + : q_ptr(&object) +{ +} + +//----------------------------------------------------------------------------- +qMRMLVirtualRealityDataModuleWidgetPrivate::~qMRMLVirtualRealityDataModuleWidgetPrivate() +{ +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityDataModuleWidgetPrivate::init() +{ + Q_Q(qMRMLVirtualRealityDataModuleWidget); + this->setupUi(q); + + // Customize widget contents + this->SubjectHierarchyTreeView->setColumnHidden(this->SubjectHierarchyTreeView->model()->idColumn(), true); +} + +//----------------------------------------------------------------------------- +// qMRMLVirtualRealityDataModuleWidget methods + +//----------------------------------------------------------------------------- +qMRMLVirtualRealityDataModuleWidget::qMRMLVirtualRealityDataModuleWidget(QWidget* _parent) + : qMRMLWidget(_parent) + , d_ptr(new qMRMLVirtualRealityDataModuleWidgetPrivate(*this)) +{ + Q_D(qMRMLVirtualRealityDataModuleWidget); + d->init(); + + this->updateWidgetFromMRML(); +} + +//----------------------------------------------------------------------------- +qMRMLVirtualRealityDataModuleWidget::~qMRMLVirtualRealityDataModuleWidget() += default; + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityDataModuleWidget::updateWidgetFromMRML() +{ + //Q_D(qMRMLVirtualRealityDataModuleWidget); +} diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityDataModuleWidget.h b/VirtualReality/Widgets/qMRMLVirtualRealityDataModuleWidget.h new file mode 100644 index 0000000..16173b1 --- /dev/null +++ b/VirtualReality/Widgets/qMRMLVirtualRealityDataModuleWidget.h @@ -0,0 +1,68 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through the Applied Cancer Research Unit program of Cancer Care + Ontario with funds provided by the Ontario Ministry of Health and Long-Term Care + and CANARIE. + +==============================================================================*/ + +#ifndef __qMRMLVirtualRealityDataModuleWidget_h +#define __qMRMLVirtualRealityDataModuleWidget_h + +// VirtualReality Widgets includes +#include "qSlicerVirtualRealityModuleWidgetsExport.h" + +// MRMLWidgets includes +#include "qMRMLWidget.h" + +// CTK includes +#include +#include + +// MRML includes +#include "qMRMLSubjectHierarchyTreeView.h" +#include "qMRMLSubjectHierarchyModel.h" + +class qMRMLVirtualRealityDataModuleWidgetPrivate; + +/// \ingroup SlicerVirtualReality_Widgets +class Q_SLICER_QTMODULES_VIRTUALREALITY_WIDGETS_EXPORT qMRMLVirtualRealityDataModuleWidget : public qMRMLWidget +{ + Q_OBJECT + QVTK_OBJECT + +public: + typedef qMRMLWidget Superclass; + /// Constructor + explicit qMRMLVirtualRealityDataModuleWidget(QWidget* parent = nullptr); + /// Destructor + ~qMRMLVirtualRealityDataModuleWidget() override; + +public slots: + +protected slots: + /// Update widgets from the MRML node + void updateWidgetFromMRML(); + +protected: + QScopedPointer d_ptr; + +private: + Q_DECLARE_PRIVATE(qMRMLVirtualRealityDataModuleWidget); + Q_DISABLE_COPY(qMRMLVirtualRealityDataModuleWidget); +}; + +#endif diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.cxx b/VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.cxx new file mode 100644 index 0000000..a3704ce --- /dev/null +++ b/VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.cxx @@ -0,0 +1,423 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through the Applied Cancer Research Unit program of Cancer Care + Ontario with funds provided by the Ontario Ministry of Health and Long-Term Care + and CANARIE. + +==============================================================================*/ + +// VirtualReality Widgets includes +#include "qMRMLVirtualRealityHomeWidget.h" +#include "ui_qMRMLVirtualRealityHomeWidget.h" +#include "qMRMLVirtualRealityDataModuleWidget.h" +#include "qMRMLVirtualRealitySegmentEditorWidget.h" + +// VirtualReality MRML includes +#include "vtkMRMLVirtualRealityViewNode.h" + +// Segmentations includes +#include "vtkMRMLSegmentEditorNode.h" + +// VTK includes +#include + +// Qt includes +#include +#include + +//----------------------------------------------------------------------------- +class qMRMLVirtualRealityHomeWidgetPrivate : public Ui_qMRMLVirtualRealityHomeWidget +{ + Q_DECLARE_PUBLIC(qMRMLVirtualRealityHomeWidget); + +protected: + qMRMLVirtualRealityHomeWidget* const q_ptr; + +public: + qMRMLVirtualRealityHomeWidgetPrivate(qMRMLVirtualRealityHomeWidget& object); + virtual ~qMRMLVirtualRealityHomeWidgetPrivate(); + + void init(); + + /// Association between VR module buttons and the corresponding widgets + QMap ModuleWidgetsMap; + +public: + /// Virtual reality view MRML node + vtkWeakPointer VirtualRealityViewNode; + qMRMLVirtualRealityDataModuleWidget* DataModuleWidget; + qMRMLVirtualRealitySegmentEditorWidget* SegmentEditorWidget; +}; + +//----------------------------------------------------------------------------- +qMRMLVirtualRealityHomeWidgetPrivate::qMRMLVirtualRealityHomeWidgetPrivate(qMRMLVirtualRealityHomeWidget& object) + : q_ptr(&object) +{ + this->VirtualRealityViewNode = nullptr; + this->DataModuleWidget = nullptr; + this->SegmentEditorWidget = nullptr; +} + +//----------------------------------------------------------------------------- +qMRMLVirtualRealityHomeWidgetPrivate::~qMRMLVirtualRealityHomeWidgetPrivate() +{ + this->VirtualRealityViewNode = nullptr; +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidgetPrivate::init() +{ + Q_Q(qMRMLVirtualRealityHomeWidget); + this->setupUi(q); + + this->backButton->setVisible(false); + + QObject::connect(this->MotionSensitivitySliderWidget, SIGNAL(valueChanged(double)), q, SLOT(onMotionSensitivityChanged(double))); + QObject::connect(this->FlySpeedSliderWidget, SIGNAL(valueChanged(double)), q, SLOT(onFlySpeedChanged(double))); + QObject::connect(this->Magnification001xButton, SIGNAL(clicked()), q, SLOT(onMagnification001xPressed())); + QObject::connect(this->Magnification01xButton, SIGNAL(clicked()), q, SLOT(onMagnification01xPressed())); + QObject::connect(this->Magnification1xButton, SIGNAL(clicked()), q, SLOT(onMagnification1xPressed())); + QObject::connect(this->Magnification10xButton, SIGNAL(clicked()), q, SLOT(onMagnification10xPressed())); + QObject::connect(this->Magnification100xButton, SIGNAL(clicked()), q, SLOT(onMagnification100xPressed())); + QObject::connect(this->SyncViewToReferenceViewButton, SIGNAL(clicked()), q, SLOT(updateViewFromReferenceViewCamera())); + + //QObject::connect(this->LockMagnificationCheckBox, SIGNAL(toggled(bool)), q, SLOT(setMagnificationLock(bool))); + //TODO: Magnification lock of view node not implemented yet + + // Hide module widget frame. It appears with the module when a module button is clicked + this->ModuleWidgetFrame->setVisible(false); + // Setup module widget frame layout + QVBoxLayout* moduleWidgetFrameLayout = new QVBoxLayout(this->ModuleWidgetFrame); + + // Register default VR modules + q->registerDefaultModules(); +} + +//----------------------------------------------------------------------------- +// qMRMLVirtualRealityHomeWidget methods + +//----------------------------------------------------------------------------- + +qMRMLVirtualRealityHomeWidget::qMRMLVirtualRealityHomeWidget(QWidget* _parent) + : qMRMLWidget(_parent) + , d_ptr(new qMRMLVirtualRealityHomeWidgetPrivate(*this)) +{ + Q_D(qMRMLVirtualRealityHomeWidget); + d->init(); + this->updateWidgetFromMRML(); +} + +//----------------------------------------------------------------------------- +qMRMLVirtualRealityHomeWidget::~qMRMLVirtualRealityHomeWidget() += default; + +//----------------------------------------------------------------------------- +vtkMRMLVirtualRealityViewNode* qMRMLVirtualRealityHomeWidget::virtualRealityViewNode() const +{ + Q_D(const qMRMLVirtualRealityHomeWidget); + return d->VirtualRealityViewNode; +} + +//----------------------------------------------------------------------------- +QString qMRMLVirtualRealityHomeWidget::virtualRealityViewNodeID()const +{ + Q_D(const qMRMLVirtualRealityHomeWidget); + return (d->VirtualRealityViewNode.GetPointer() ? d->VirtualRealityViewNode->GetID() : QString()); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::setVirtualRealityViewNode(vtkMRMLVirtualRealityViewNode* node) +{ + Q_D(qMRMLVirtualRealityHomeWidget); + + if (d->VirtualRealityViewNode == node) + { + return; + } + + qvtkReconnect(d->VirtualRealityViewNode, node, vtkCommand::ModifiedEvent, this, SLOT(updateWidgetFromMRML())); + + vtkMRMLVirtualRealityViewNode* vrViewNode = vtkMRMLVirtualRealityViewNode::SafeDownCast(node); + d->VirtualRealityViewNode = vrViewNode; + + this->updateWidgetFromMRML(); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::updateWidgetFromMRML() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + vtkMRMLVirtualRealityViewNode* vrViewNode = d->VirtualRealityViewNode; + + bool wasBlocked = d->MotionSensitivitySliderWidget->blockSignals(true); + d->MotionSensitivitySliderWidget->setValue(vrViewNode != nullptr ? vrViewNode->GetMotionSensitivity() * 100.0 : 0); + d->MotionSensitivitySliderWidget->setEnabled(vrViewNode != nullptr); + d->MotionSensitivitySliderWidget->blockSignals(wasBlocked); + + wasBlocked = d->FlySpeedSliderWidget->blockSignals(true); + d->FlySpeedSliderWidget->setValue(vrViewNode != nullptr ? vrViewNode->GetMotionSpeed() : 1.6666); + d->FlySpeedSliderWidget->setEnabled(vrViewNode != nullptr); + d->FlySpeedSliderWidget->blockSignals(wasBlocked); + + /* + bool wasBlocked = d->LockMagnificationCheckBox->blockSignals(true); + d->LockMagnificationCheckBox->setChecked(vrViewNode != nullptr && vrViewNode->); + d->LockMagnificationCheckBox->setEnabled(vrViewNode != nullptr); + d->LockMagnificationCheckBox->blockSignals(wasBlocked); + */ + //TODO: Magnification lock of view node not implemented yet + + d->Magnification001xButton->setEnabled(vrViewNode != nullptr && vrViewNode->GetMagnification() != NULL); + d->Magnification01xButton->setEnabled(vrViewNode != nullptr && vrViewNode->GetMagnification() != NULL); + d->Magnification1xButton->setEnabled(vrViewNode != nullptr && vrViewNode->GetMagnification() != NULL); + d->Magnification10xButton->setEnabled(vrViewNode != nullptr && vrViewNode->GetMagnification() != NULL); + d->Magnification100xButton->setEnabled(vrViewNode != nullptr && vrViewNode->GetMagnification() != NULL); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::onMotionSensitivityChanged(double percent) +{ + Q_D(qMRMLVirtualRealityHomeWidget); + vtkMRMLVirtualRealityViewNode* vrViewNode = d->VirtualRealityViewNode; + + if (!vrViewNode) + { + qCritical() << Q_FUNC_INFO << " Failed: view node is null"; + return; + } + vrViewNode->SetMotionSensitivity(percent * 0.01); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::onFlySpeedChanged(double speedMps) +{ + Q_D(qMRMLVirtualRealityHomeWidget); + vtkMRMLVirtualRealityViewNode* vrViewNode = d->VirtualRealityViewNode; + + if (!vrViewNode) + { + qCritical() << Q_FUNC_INFO << " Failed: view node is null"; + return; + } + vrViewNode->SetMotionSpeed(speedMps); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::onMagnification001xPressed() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + vtkMRMLVirtualRealityViewNode* vrViewNode = d->VirtualRealityViewNode; + + if (!vrViewNode) + { + qCritical() << Q_FUNC_INFO << " Failed: view node is null"; + return; + } + vrViewNode->SetMagnification(0.01); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::onMagnification01xPressed() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + vtkMRMLVirtualRealityViewNode* vrViewNode = d->VirtualRealityViewNode; + + if (!vrViewNode) + { + qCritical() << Q_FUNC_INFO << " Failed: view node is null"; + return; + } + vrViewNode->SetMagnification(0.1); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::onMagnification1xPressed() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + vtkMRMLVirtualRealityViewNode* vrViewNode = d->VirtualRealityViewNode; + + if (!vrViewNode) + { + qCritical() << Q_FUNC_INFO << " Failed: view node is null"; + return; + } + vrViewNode->SetMagnification(1.0); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::onMagnification10xPressed() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + vtkMRMLVirtualRealityViewNode* vrViewNode = d->VirtualRealityViewNode; + + if (!vrViewNode) + { + qCritical() << Q_FUNC_INFO << " Failed: view node is null"; + return; + } + vrViewNode->SetMagnification(10.0); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::onMagnification100xPressed() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + vtkMRMLVirtualRealityViewNode* vrViewNode = d->VirtualRealityViewNode; + + if (!vrViewNode) + { + qCritical() << Q_FUNC_INFO << " Failed: view node is null"; + return; + } + vrViewNode->SetMagnification(100.0); +} + +//----------------------------------------------------------------------------- +/* +void qMRMLVirtualRealityHomeWidget::updateViewFromReferenceViewCamera() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + qSlicerVirtualRealityModule* vrModule = dynamic_cast(this->module()); + if (!vrModule) + { + qCritical() << Q_FUNC_INFO << " Failed: vrModule is null"; + return; + } + qMRMLVirtualRealityView* vrView = vrModule->viewWidget(); + if (!vrView) + { + qCritical() << Q_FUNC_INFO << " Failed: view node is null"; + return; + } + vrView->updateViewFromReferenceViewCamera(); +} +*/ +//TODO: This member function won't work unless qSlicerVirtualRealityModule and qMRMLVirtualRealityView are included + +//----------------------------------------------------------------------------- +/* +void qMRMLVirtualRealityHomeWidget::setMagnificationLock(bool active) +{ + Q_D(qMRMLVirtualRealityHomeWidget); + vtkMRMLVirtualRealityViewNode* vrViewNode = d->VirtualRealityViewNode; + + if (!vrViewNode) + { + qCritical() << Q_FUNC_INFO << " Failed: view node is null"; + return; + } + + vrViewNode->????(active); //TODO: Implement magnification lock for view node +} +*/ + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::addModuleButton(QWidget* moduleWidget, QIcon& icon) +{ + Q_D(qMRMLVirtualRealityHomeWidget); + + if (!moduleWidget) + { + qCritical() << Q_FUNC_INFO << ": Invalid module widget given"; + return; + } + + // Create button for the new module + QPushButton* moduleButton = new QPushButton(d->ModulesGroupBox); + d->ModulesGroupBoxLayout->addWidget(moduleButton); + moduleButton->setIcon(icon); + + // Setup module widget within home widget + moduleWidget->setParent(d->ModuleWidgetFrame); + moduleWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + moduleWidget->setVisible(false); + d->ModuleWidgetFrame->layout()->addWidget(moduleWidget); + d->ModuleWidgetsMap[moduleButton] = moduleWidget; + + // Handle scene if a MRML widget is given + qMRMLWidget* moduleMrmlWidget = qobject_cast(moduleWidget); + if (moduleMrmlWidget) + { + moduleMrmlWidget->setMRMLScene(this->mrmlScene()); + QObject::connect(this, SIGNAL(mrmlSceneChanged(vtkMRMLScene*)), moduleWidget, SLOT(setMRMLScene(vtkMRMLScene*))); + } + + QObject::connect(moduleButton, SIGNAL(clicked()), this, SLOT(onModuleButtonClicked())); + QObject::connect(d->backButton, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); + + moduleButton->setVisible(true); + d->backButton->setVisible(false); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::onModuleButtonClicked() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + + QPushButton* moduleButton = qobject_cast(sender()); + if (!moduleButton) + { + qCritical() << Q_FUNC_INFO << ": Failed to access clicked button"; + return; + } + + d->HomeWidgetFrame->setVisible(false); + d->ModulesGroupBox->setVisible(false); + + d->ModuleWidgetFrame->setVisible(true); + d->ModuleWidgetsMap[moduleButton]->setVisible(true); + d->backButton->setVisible(true); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::onBackButtonClicked() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + + QMap::const_iterator buttonIt; + for (buttonIt = d->ModuleWidgetsMap.constBegin(); buttonIt != d->ModuleWidgetsMap.constEnd(); ++buttonIt) + { + buttonIt.value()->setVisible(false); + } + d->ModuleWidgetFrame->setVisible(false); + d->backButton->setVisible(false); + + d->HomeWidgetFrame->setVisible(true); + d->ModulesGroupBox->setVisible(true); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::registerDefaultModules() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + + d->DataModuleWidget = new qMRMLVirtualRealityDataModuleWidget(this); + d->DataModuleWidget->setMRMLScene(this->mrmlScene()); + d->SegmentEditorWidget = new qMRMLVirtualRealitySegmentEditorWidget(this); + d->SegmentEditorWidget->setMRMLScene(this->mrmlScene()); + + QIcon dataIcon(QPixmap(":/Icons/SubjectHierarchy.png")); + QIcon segmentEditorIcon(QPixmap(":/Icons/SegmentEditor.png")); + + this->addModuleButton(d->DataModuleWidget, dataIcon); + this->addModuleButton(d->SegmentEditorWidget, segmentEditorIcon); +} + +void qMRMLVirtualRealityHomeWidget::setMRMLSegmentEditorNode(vtkMRMLSegmentEditorNode* newSegmentEditorNode) +{ + Q_D(qMRMLVirtualRealityHomeWidget); + d->SegmentEditorWidget->setMRMLSegmentEditorNode(newSegmentEditorNode); +} diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.h b/VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.h new file mode 100644 index 0000000..31e4559 --- /dev/null +++ b/VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.h @@ -0,0 +1,101 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through the Applied Cancer Research Unit program of Cancer Care + Ontario with funds provided by the Ontario Ministry of Health and Long-Term Care + and CANARIE. + +==============================================================================*/ + +#ifndef __qMRMLVirtualRealityHomeWidget_h +#define __qMRMLVirtualRealityHomeWidget_h + +// VirtualReality Widgets includes +#include "qSlicerVirtualRealityModuleWidgetsExport.h" + +// MRMLWidgets includes +#include "qMRMLWidget.h" + +// CTK includes +#include +#include + +// Qt includes +#include +#include + +class vtkMRMLVirtualRealityViewNode; +class qMRMLVirtualRealityHomeWidgetPrivate; +class vtkMRMLSegmentEditorNode; + +/// \ingroup SlicerVirtualReality_Widgets +class Q_SLICER_QTMODULES_VIRTUALREALITY_WIDGETS_EXPORT qMRMLVirtualRealityHomeWidget : public qMRMLWidget +{ + Q_OBJECT + QVTK_OBJECT + +public: + typedef qMRMLWidget Superclass; + /// Constructor + explicit qMRMLVirtualRealityHomeWidget(QWidget* parent = nullptr); + /// Destructor + ~qMRMLVirtualRealityHomeWidget() override; + + /// Get virtual reality view MRML node + Q_INVOKABLE vtkMRMLVirtualRealityViewNode* virtualRealityViewNode()const; + /// Get virtual reality view MRML node ID + Q_INVOKABLE QString virtualRealityViewNodeID()const; + + /// Add new button for a given VR module widget + /// \param moduleWidget The widget that appears in place of the home widget when its button is pressed + /// \param icon The icon that appears on the button in the home widget + Q_INVOKABLE void addModuleButton(QWidget* moduleWidget, QIcon& icon); + + /// Register default modules: Data, Segment Editor + Q_INVOKABLE void registerDefaultModules(); + + /// Set the segment editor node for the segment editor widget + Q_INVOKABLE void setMRMLSegmentEditorNode(vtkMRMLSegmentEditorNode* newSegmentEditorNode); + +public slots: + /// Set virtual reality view MRML node + void setVirtualRealityViewNode(vtkMRMLVirtualRealityViewNode* node); + + void onMotionSensitivityChanged(double); + void onFlySpeedChanged(double); + void onMagnification001xPressed(); + void onMagnification01xPressed(); + void onMagnification1xPressed(); + void onMagnification10xPressed(); + void onMagnification100xPressed(); + //void updateViewFromReferenceViewCamera(); + //void setMagnificationLock(bool); + + void onModuleButtonClicked(); + void onBackButtonClicked(); + +protected slots: + /// Update widgets from the MRML node + void updateWidgetFromMRML(); + +protected: + QScopedPointer d_ptr; + +private: + Q_DECLARE_PRIVATE(qMRMLVirtualRealityHomeWidget); + Q_DISABLE_COPY(qMRMLVirtualRealityHomeWidget); +}; + +#endif diff --git a/VirtualReality/Widgets/qMRMLVirtualRealitySegmentEditorWidget.cxx b/VirtualReality/Widgets/qMRMLVirtualRealitySegmentEditorWidget.cxx new file mode 100644 index 0000000..5640241 --- /dev/null +++ b/VirtualReality/Widgets/qMRMLVirtualRealitySegmentEditorWidget.cxx @@ -0,0 +1,118 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through the Applied Cancer Research Unit program of Cancer Care + Ontario with funds provided by the Ontario Ministry of Health and Long-Term Care + and CANARIE. + +==============================================================================*/ + +// VirtualReality Widgets includes +#include "qMRMLVirtualRealitySegmentEditorWidget.h" +#include "ui_qMRMLVirtualRealitySegmentEditorWidget.h" + +// VirtualReality MRML includes +#include "vtkMRMLVirtualRealityViewNode.h" + +// Segmentations includes +#include "vtkMRMLSegmentEditorNode.h" + +// Qt includes +#include +#include "qpushbutton.h" +#include "ctkMenuButton.h" +#include "qtoolbutton.h" + +//----------------------------------------------------------------------------- +class qMRMLVirtualRealitySegmentEditorWidgetPrivate : public Ui_qMRMLVirtualRealitySegmentEditorWidget +{ + Q_DECLARE_PUBLIC(qMRMLVirtualRealitySegmentEditorWidget); + +protected: + qMRMLVirtualRealitySegmentEditorWidget* const q_ptr; + +public: + qMRMLVirtualRealitySegmentEditorWidgetPrivate(qMRMLVirtualRealitySegmentEditorWidget& object); + virtual ~qMRMLVirtualRealitySegmentEditorWidgetPrivate(); + + void init(); +}; + +//----------------------------------------------------------------------------- +qMRMLVirtualRealitySegmentEditorWidgetPrivate::qMRMLVirtualRealitySegmentEditorWidgetPrivate(qMRMLVirtualRealitySegmentEditorWidget& object) + : q_ptr(&object) +{ +} + +//----------------------------------------------------------------------------- +qMRMLVirtualRealitySegmentEditorWidgetPrivate::~qMRMLVirtualRealitySegmentEditorWidgetPrivate() +{ +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealitySegmentEditorWidgetPrivate::init() +{ + Q_Q(qMRMLVirtualRealitySegmentEditorWidget); + this->setupUi(q); + + //List of effects to be used in VR; effects not listed here are not shown in the widget + QStringList effectNameOrder; + effectNameOrder.append("None"); + effectNameOrder.append("Paint"); + effectNameOrder.append("Erase"); + effectNameOrder.append("Level tracing"); + effectNameOrder.append("Grow from seeds"); + effectNameOrder.append("Smoothing"); + effectNameOrder.append("Scissors"); + effectNameOrder.append("Logical Operators"); + this->SegmentEditorWidget->setEffectNameOrder(effectNameOrder); + + //Hide unneeded effects and buttons + this->SegmentEditorWidget->setUnorderedEffectsVisible(false); + this->SegmentEditorWidget->setSwitchToSegmentationsButtonVisible(false); + this->SegmentEditorWidget->findChild("SpecifyGeometryButton")->setVisible(false); + this->SegmentEditorWidget->findChild("SliceRotateWarningButton")->setVisible(false); + this->SegmentEditorWidget->findChild("Show3DButton")->setVisible(false); +} + +//----------------------------------------------------------------------------- +// qMRMLVirtualRealitySegmentEditorWidget methods + +//----------------------------------------------------------------------------- +qMRMLVirtualRealitySegmentEditorWidget::qMRMLVirtualRealitySegmentEditorWidget(QWidget* _parent) + : qMRMLWidget(_parent) + , d_ptr(new qMRMLVirtualRealitySegmentEditorWidgetPrivate(*this)) +{ + Q_D(qMRMLVirtualRealitySegmentEditorWidget); + d->init(); + + this->updateWidgetFromMRML(); +} + +//----------------------------------------------------------------------------- +qMRMLVirtualRealitySegmentEditorWidget::~qMRMLVirtualRealitySegmentEditorWidget() += default; + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealitySegmentEditorWidget::updateWidgetFromMRML() +{ + //Q_D(qMRMLVirtualRealitySegmentEditorWidget); +} + +void qMRMLVirtualRealitySegmentEditorWidget::setMRMLSegmentEditorNode(vtkMRMLSegmentEditorNode* newSegmentEditorNode) +{ + Q_D(qMRMLVirtualRealitySegmentEditorWidget); + d->SegmentEditorWidget->setMRMLSegmentEditorNode(newSegmentEditorNode); +} diff --git a/VirtualReality/Widgets/qMRMLVirtualRealitySegmentEditorWidget.h b/VirtualReality/Widgets/qMRMLVirtualRealitySegmentEditorWidget.h new file mode 100644 index 0000000..93c10f9 --- /dev/null +++ b/VirtualReality/Widgets/qMRMLVirtualRealitySegmentEditorWidget.h @@ -0,0 +1,67 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through the Applied Cancer Research Unit program of Cancer Care + Ontario with funds provided by the Ontario Ministry of Health and Long-Term Care + and CANARIE. + +==============================================================================*/ + +#ifndef __qMRMLVirtualRealitySegmentEditorWidget_h +#define __qMRMLVirtualRealitySegmentEditorWidget_h + +// VirtualReality Widgets includes +#include "qSlicerVirtualRealityModuleWidgetsExport.h" + +// MRMLWidgets includes +#include "qMRMLWidget.h" + +// CTK includes +#include +#include + +class qMRMLVirtualRealitySegmentEditorWidgetPrivate; +class vtkMRMLSegmentEditorNode; + +/// \ingroup SlicerVirtualReality_Widgets +class Q_SLICER_QTMODULES_VIRTUALREALITY_WIDGETS_EXPORT qMRMLVirtualRealitySegmentEditorWidget : public qMRMLWidget +{ + Q_OBJECT + QVTK_OBJECT + +public: + typedef qMRMLWidget Superclass; + /// Constructor + explicit qMRMLVirtualRealitySegmentEditorWidget(QWidget* parent = nullptr); + /// Destructor + ~qMRMLVirtualRealitySegmentEditorWidget() override; + + void setMRMLSegmentEditorNode(vtkMRMLSegmentEditorNode* newSegmentEditorNode); + +public slots: + +protected slots: + /// Update widgets from the MRML node + void updateWidgetFromMRML(); + +protected: + QScopedPointer d_ptr; + +private: + Q_DECLARE_PRIVATE(qMRMLVirtualRealitySegmentEditorWidget); + Q_DISABLE_COPY(qMRMLVirtualRealitySegmentEditorWidget); +}; + +#endif diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityView.cxx b/VirtualReality/Widgets/qMRMLVirtualRealityView.cxx index ef2fbae..556caf5 100644 --- a/VirtualReality/Widgets/qMRMLVirtualRealityView.cxx +++ b/VirtualReality/Widgets/qMRMLVirtualRealityView.cxx @@ -56,8 +56,9 @@ #include "vtkSlicerConfigure.h" // For Slicer_USE_OpenVR #include "vtkSlicerCamerasModuleLogic.h" -// VirtualReality includes +// VirtualReality MRML includes #include "vtkMRMLVirtualRealityViewNode.h" +#include "vtkMRMLThreeDViewInteractorStyle.h" // MRMLDisplayableManager includes #include @@ -114,8 +115,9 @@ namespace qMRMLVirtualRealityViewPrivate::qMRMLVirtualRealityViewPrivate(qMRMLVirtualRealityView& object) : q_ptr(&object) , CamerasLogic(nullptr) + , MRMLVirtualRealityViewNode(nullptr) + , HomeWidget(nullptr) { - this->MRMLVirtualRealityViewNode = nullptr; } //--------------------------------------------------------------------------- @@ -127,6 +129,10 @@ qMRMLVirtualRealityViewPrivate::~qMRMLVirtualRealityViewPrivate() void qMRMLVirtualRealityViewPrivate::init() { QObject::connect(&this->VirtualRealityLoopTimer, SIGNAL(timeout()), this, SLOT(doOpenVirtualReality())); + + // Setup VR home widget + this->HomeWidget = new qMRMLVirtualRealityHomeWidget(q_ptr); + QObject::connect(this, SIGNAL(mrmlSceneChanged(vtkMRMLScene*)), this->HomeWidget, SLOT(setMRMLScene(vtkMRMLScene*))); } //---------------------------------------------------------------------------- @@ -691,6 +697,14 @@ qMRMLVirtualRealityView::~qMRMLVirtualRealityView() { } +//------------------------------------------------------------------------------ +void qMRMLVirtualRealityView::registerModule(QWidget* widget, QIcon& icon) +{ + Q_D(qMRMLVirtualRealityView); + + d->HomeWidget->addModuleButton(widget, icon); +} + //------------------------------------------------------------------------------ void qMRMLVirtualRealityView::addDisplayableManager(const QString& displayableManagerName) { @@ -730,6 +744,13 @@ vtkMRMLVirtualRealityViewNode* qMRMLVirtualRealityView::mrmlVirtualRealityViewNo return d->MRMLVirtualRealityViewNode; } +//---------------------------------------------------------------------------- +qMRMLVirtualRealityHomeWidget* qMRMLVirtualRealityView::vrHomeWidget()const +{ + Q_D(const qMRMLVirtualRealityView); + return d->HomeWidget; +} + //------------------------------------------------------------------------------ void qMRMLVirtualRealityView::getDisplayableManagers(vtkCollection* displayableManagers) { @@ -1006,3 +1027,17 @@ void qMRMLVirtualRealityView::updateViewFromReferenceViewCamera() ren->ResetCameraClippingRange(); } + +//------------------------------------------------------------------------------ +void qMRMLVirtualRealityView::setVirtualWidget(QWidget* menuWidget) +{ + QPixmap menuTexture(menuWidget->size()); + //TODO: Set VR style sheet on widget (large text etc) + menuWidget->render(&menuTexture); + + bool errorCheck = menuTexture.save("menuTextureImage.png", "PNG", 100); + if (!errorCheck) + { + qCritical() << Q_FUNC_INFO << ": Error while saving menu texture"; + } +} diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityView.h b/VirtualReality/Widgets/qMRMLVirtualRealityView.h index d7fc2d7..dc78e23 100644 --- a/VirtualReality/Widgets/qMRMLVirtualRealityView.h +++ b/VirtualReality/Widgets/qMRMLVirtualRealityView.h @@ -31,8 +31,9 @@ #include "qSlicerVirtualRealityModuleWidgetsExport.h" // CTK includes -#include +#include +class qMRMLVirtualRealityHomeWidget; class qMRMLVirtualRealityViewPrivate; class vtkMRMLVirtualRealityViewNode; class vtkCollection; @@ -84,6 +85,9 @@ class Q_SLICER_QTMODULES_VIRTUALREALITY_WIDGETS_EXPORT qMRMLVirtualRealityView : /// Get the 3D View node observed by view. Q_INVOKABLE vtkMRMLVirtualRealityViewNode* mrmlVirtualRealityViewNode()const; + /// Get VR home widget + Q_INVOKABLE qMRMLVirtualRealityHomeWidget* vrHomeWidget()const; + /// Get a reference to the associated vtkRenderer vtkOpenVRRenderer* renderer()const; @@ -121,6 +125,10 @@ class Q_SLICER_QTMODULES_VIRTUALREALITY_WIDGETS_EXPORT qMRMLVirtualRealityView : Q_INVOKABLE void setGestureButtonToNone(); ///@} + /// Register VR module. A button will be added in the modules section of the VR home + /// widget with the provided icon. When clicked, the given widget will be shown + void registerModule(QWidget* widget, QIcon& icon); + signals: void physicalToWorldMatrixModified(); @@ -150,8 +158,10 @@ public slots: void onPhysicalToWorldMatrixModified(); void onButton3DEvent(vtkObject* caller, void* call_data, unsigned long vtk_event, void* client_data); -protected: + /// Set widget that is being shown on the "tablet panel" in virtual reality + void setVirtualWidget(QWidget*); +protected: QScopedPointer d_ptr; private: diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityView_p.h b/VirtualReality/Widgets/qMRMLVirtualRealityView_p.h index b224f43..44aef9e 100644 --- a/VirtualReality/Widgets/qMRMLVirtualRealityView_p.h +++ b/VirtualReality/Widgets/qMRMLVirtualRealityView_p.h @@ -40,6 +40,9 @@ #include #include +// VirtualReality Widgets includes +#include "qMRMLVirtualRealityHomeWidget.h" + // qMRML includes #include "qMRMLVirtualRealityView.h" @@ -115,6 +118,8 @@ public slots: double LastViewPosition[3]; QTimer VirtualRealityLoopTimer; + + qMRMLVirtualRealityHomeWidget* HomeWidget; }; #endif diff --git a/VirtualReality/qSlicerHomeVirtualWidget.cxx b/VirtualReality/qSlicerHomeVirtualWidget.cxx new file mode 100644 index 0000000..9d36104 --- /dev/null +++ b/VirtualReality/qSlicerHomeVirtualWidget.cxx @@ -0,0 +1,93 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==============================================================================*/ + +#include +#include "qSlicerHomeVirtualReality.h" + +// Qt includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//----------------------------------------------------------------------------- +// qSlicerHomeVirtualWidget Methods + +//----------------------------------------------------------------------------- +qSlicerHomeVirtualWidget::qSlicerHomeVirtualWidget(QWidget *parent) +{ + this->minimumHeight(650); + this->minimumWidth(980); + this->resize(dimensions::height, dimensions::width); + + //Create all the Qlabels + QLabel *flySpeedLabel = new QLabel("Fly Speed:", this); + QLabel *magnificationLabel = new QLabel("Magnification:", this); + QLabel *motionSenLabel = new QLabel("Motion Sensitivity:", this; + QLabel *lightingLabel = new QLabel("Lighting:", this); + + //Create all the push buttons + QPushButton *syncView = new QPushButton("Sync view to Reference View", this); + QPushButton *magnificationButton1 = new QPushButton("0.5x", this); + QPushButton *magnificationButton2 = new QPushButton("1x", this); + QPushButton *magnificationButton3 = new QPushButton("2x", this); + QPushButton *magnificationButton4 = new QPushButton("40x", this); + QPushButton *twoSidedLighting = new QPushButton("Two-sided Lighting", this); + QPushButton *backLighting = new QPushButton("Back Lighting", this); + + //create all sliders + QSlider *flySpeedSlider = new QSlider(Qt::Horizontal, this); + QSlider *motionSenSlider = new QSlider(Qt::Horizontal, this); + + //create the layouts for the UI + QFormLayout *menuLayout = new QFormLayout(this); + QHBoxLayout *magnificationButtonLayout = new QHBoxLayout(this); + QHBoxLayout *lightingButtonLayout = new QHBoxLayout(this); + + //place Qwidgets in appropriate layouts + magnificationButtonLayout->addWidget(magnificationButton1); + magnificationButtonLayout->addWidget(magnificationButton2); + magnificationButtonLayout->addWidget(magnificationButton3); + magnificationButtonLayout->addWidget(magnificationButton4); + lightingButtonLayout->addWidget(twoSidedLighting); + lightingButtonLayout->addWidget(backLighting); + + //set up the main form layout + menuLayout->addRow(flySpeedLabel, flySpeedSlider); + menuLayout->addRow(magnificationLabel, magnificationButtonLayout); + menuLayout->addRow(motionSenLabel, motionSenSlider); + menuLayout->addRow(lightingLabel, lightingButtonLayout); + + //spacing and positioning + menuLayout->setHorizontalSpacing(20); + menuLayout->setVerticleSpacing(99); +} +//--------------------------------------------------------------- +qSlicerHomeVirtualWidget::~qSlicerHomeVirtualWidget() +{ +}; \ No newline at end of file diff --git a/VirtualReality/qSlicerHomeVirtualWidget.h b/VirtualReality/qSlicerHomeVirtualWidget.h new file mode 100644 index 0000000..c27ba39 --- /dev/null +++ b/VirtualReality/qSlicerHomeVirtualWidget.h @@ -0,0 +1,43 @@ +/*============================================================================== + + Program: 3D Slicer + + Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==============================================================================*/ + +#ifndef __qSlicerHomeVirtualWidget_h +#define __qSlicerHomeVirtualWidget_h + +// SlicerQt includes +//Qt includes +#include +#include + +namespace dimensions +{ + const int height = 688; + const int width = 980; +} + +class qSlicerHomeVirtualWidget : public QWidget +{ + Q_OBJECT +public: + qSlicerHomeVirtualWidget(QWidget *parent = 0); + ~qSlicerHomeVirtualWidget(); +public slots: + +}; + + +#endif