From 691692fc1342d4293e40e3523eeb46e310298bf4 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 Squashed commits: 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 KitwareMedical#43 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 KitwareMedical#43 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. KitwareMedical#43 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! --- .../Resources/menuTextureImage2.png | Bin 0 -> 2026761 bytes VirtualReality/Widgets/CMakeLists.txt | 5 + .../Resources/StyleSheets/VrWidgetStyle.qss | 50 +++ .../UI/qMRMLVirtualRealityHomeWidget.ui | 206 ++++++++++ .../qSlicerVirtualRealityModuleWidgets.qrc | 5 + .../Widgets/qMRMLVirtualRealityHomeWidget.cxx | 367 ++++++++++++++++++ .../Widgets/qMRMLVirtualRealityHomeWidget.h | 89 +++++ .../Widgets/qMRMLVirtualRealityView.cxx | 34 +- .../Widgets/qMRMLVirtualRealityView.h | 9 +- .../Widgets/qMRMLVirtualRealityView_p.h | 5 + VirtualReality/qSlicerHomeVirtualWidget.cxx | 93 +++++ VirtualReality/qSlicerHomeVirtualWidget.h | 43 ++ 12 files changed, 902 insertions(+), 4 deletions(-) create mode 100644 VirtualReality/Resources/menuTextureImage2.png create mode 100644 VirtualReality/Widgets/Resources/StyleSheets/VrWidgetStyle.qss create mode 100644 VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealityHomeWidget.ui create mode 100644 VirtualReality/Widgets/Resources/qSlicerVirtualRealityModuleWidgets.qrc create mode 100644 VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.cxx create mode 100644 VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.h create mode 100644 VirtualReality/qSlicerHomeVirtualWidget.cxx create mode 100644 VirtualReality/qSlicerHomeVirtualWidget.h 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/Widgets/CMakeLists.txt b/VirtualReality/Widgets/CMakeLists.txt index 1a8f336..c90eb7a 100644 --- a/VirtualReality/Widgets/CMakeLists.txt +++ b/VirtualReality/Widgets/CMakeLists.txt @@ -19,19 +19,24 @@ set(${KIT}_SRCS qMRML${MODULE_NAME}View.h qMRML${MODULE_NAME}TransformWidget.cxx qMRML${MODULE_NAME}TransformWidget.h + qMRMLVirtualRealityHomeWidget.cxx + qMRMLVirtualRealityHomeWidget.h ) set(${KIT}_MOC_SRCS qMRML${MODULE_NAME}View.h qMRML${MODULE_NAME}TransformWidget.h qMRML${MODULE_NAME}View_p.h + qMRMLVirtualRealityHomeWidget.h ) set(${KIT}_UI_SRCS + Resources/UI/qMRMLVirtualRealityHomeWidget.ui Resources/UI/qMRML${MODULE_NAME}TransformWidget.ui ) set(${KIT}_RESOURCES + Resources/${KIT}.qrc Resources/qMRML${MODULE_NAME}TransformWidget.qrc ) 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/qMRMLVirtualRealityHomeWidget.ui b/VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealityHomeWidget.ui new file mode 100644 index 0000000..8cd2672 --- /dev/null +++ b/VirtualReality/Widgets/Resources/UI/qMRMLVirtualRealityHomeWidget.ui @@ -0,0 +1,206 @@ + + + qMRMLVirtualRealityHomeWidget + + + + 0 + 0 + 532 + 262 + + + + 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 + + + % + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + + Modules + + + + + + + + + + + + + qMRMLWidget + QWidget +
qMRMLWidget.h
+ 1 +
+ + ctkSliderWidget + QWidget +
ctkSliderWidget.h
+
+
+ + +
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/qMRMLVirtualRealityHomeWidget.cxx b/VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.cxx new file mode 100644 index 0000000..e35b25c --- /dev/null +++ b/VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.cxx @@ -0,0 +1,367 @@ +/*============================================================================== + + 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" + +// VirtualReality MRML includes +#include "vtkMRMLVirtualRealityViewNode.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(); + + QMap ModuleWidgetsMap; + +public: + /// Virtual reality view MRML node + vtkWeakPointer VirtualRealityViewNode; +}; + +//----------------------------------------------------------------------------- +qMRMLVirtualRealityHomeWidgetPrivate::qMRMLVirtualRealityHomeWidgetPrivate(qMRMLVirtualRealityHomeWidget& object) + : q_ptr(&object) +{ + this->VirtualRealityViewNode = nullptr; +} + +//----------------------------------------------------------------------------- +qMRMLVirtualRealityHomeWidgetPrivate::~qMRMLVirtualRealityHomeWidgetPrivate() +{ +} + +//----------------------------------------------------------------------------- +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 +} + +//----------------------------------------------------------------------------- +// 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; +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::addModuleButton(QWidget* moduleWidget, QIcon& icon) +{ + Q_D(qMRMLVirtualRealityHomeWidget); + + if (!moduleWidget) + { + qCritical() << Q_FUNC_INFO << "Failed: widget is null"; + return; + } + + QPushButton* moduleButton = new QPushButton(d->ModulesGroupBox); + d->ModulesGroupBoxLayout->addWidget(moduleButton); + moduleButton->setIcon(icon); + + moduleWidget->setParent(d->ModuleWidgetFrame); + d->ModuleWidgetsMap[moduleButton] = moduleWidget; + + QObject::connect(moduleButton, SIGNAL(clicked()), this, SLOT(onModuleButtonPressed())); + QObject::connect(d->backButton, SIGNAL(clicked()), this, SLOT(onBackButtonPressed())); + + moduleButton->setVisible(true); + moduleWidget->setVisible(false); + d->backButton->setVisible(false); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::onModuleButtonPressed() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + + QPushButton* moduleButton = qobject_cast(sender()); + + if (!moduleButton) + { + qCritical() << Q_FUNC_INFO << "Failed: moduleButton is null"; + return; + } + + d->HomeWidgetFrame->setVisible(false); + d->ModulesGroupBox->setVisible(false); + d->ModuleWidgetsMap[moduleButton]->setVisible(true); + d->backButton->setVisible(true); +} + +//----------------------------------------------------------------------------- +void qMRMLVirtualRealityHomeWidget::onBackButtonPressed() +{ + Q_D(qMRMLVirtualRealityHomeWidget); + d->HomeWidgetFrame->setVisible(true); + d->ModulesGroupBox->setVisible(true); + d->backButton->setVisible(false); + + QMap::const_iterator iteratorForMap; + for (iteratorForMap = d->ModuleWidgetsMap.constBegin(); iteratorForMap != d->ModuleWidgetsMap.constEnd(); ++iteratorForMap) + { + iteratorForMap.value()->setVisible(false); + } + +} + +//----------------------------------------------------------------------------- +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 + +} +*/ diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.h b/VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.h new file mode 100644 index 0000000..470699f --- /dev/null +++ b/VirtualReality/Widgets/qMRMLVirtualRealityHomeWidget.h @@ -0,0 +1,89 @@ +/*============================================================================== + + 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; + +/// \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; + Q_INVOKABLE QString virtualRealityViewNodeID()const; + +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 addModuleButton(QWidget* moduleWidget, QIcon& icon); + void onModuleButtonPressed(); + void onBackButtonPressed(); + +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/qMRMLVirtualRealityView.cxx b/VirtualReality/Widgets/qMRMLVirtualRealityView.cxx index 9d8d0f8..4dc3da8 100644 --- a/VirtualReality/Widgets/qMRMLVirtualRealityView.cxx +++ b/VirtualReality/Widgets/qMRMLVirtualRealityView.cxx @@ -40,6 +40,7 @@ #include #include #include +#include // CTK includes #include @@ -57,7 +58,7 @@ #include "vtkSlicerCamerasModuleLogic.h" #include // For Slicer_VERSION_MAJOR, Slicer_VERSION_MINOR -// VirtualReality includes +// VirtualReality MRML includes #include "vtkMRMLVirtualRealityViewNode.h" // MRMLDisplayableManager includes @@ -120,6 +121,7 @@ qMRMLVirtualRealityViewPrivate::qMRMLVirtualRealityViewPrivate(qMRMLVirtualReali , CamerasLogic(NULL) { this->MRMLVirtualRealityViewNode = 0; + this->HomeWidget = new qMRMLVirtualRealityHomeWidget(q_ptr); } //--------------------------------------------------------------------------- @@ -621,7 +623,6 @@ void qMRMLVirtualRealityViewPrivate::updateTransformNodesWithTrackerPoses() #endif } - //---------------------------------------------------------------------------- void qMRMLVirtualRealityViewPrivate::updateTransformNodeWithPose(vtkMRMLTransformNode* node, vr::TrackedDevicePose_t& pose) { @@ -641,7 +642,6 @@ void qMRMLVirtualRealityViewPrivate::updateTransformNodeWithPose(vtkMRMLTransfor node->SetAttribute("VirtualReality.PoseStatus", PoseStatusToString(pose.eTrackingResult).c_str()); } - // -------------------------------------------------------------------------- // qMRMLVirtualRealityView methods @@ -658,6 +658,13 @@ qMRMLVirtualRealityView::~qMRMLVirtualRealityView() { } +//------------------------------------------------------------------------------ +void qMRMLVirtualRealityView::registerModule(QWidget* widget, QIcon& icon) +{ + Q_D(qMRMLVirtualRealityView); + d->HomeWidget->addModuleButton(widget, icon); +} + //------------------------------------------------------------------------------ void qMRMLVirtualRealityView::addDisplayableManager(const QString& displayableManagerName) { @@ -697,6 +704,13 @@ vtkMRMLVirtualRealityViewNode* qMRMLVirtualRealityView::mrmlVirtualRealityViewNo return d->MRMLVirtualRealityViewNode; } +//---------------------------------------------------------------------------- +qMRMLVirtualRealityHomeWidget* qMRMLVirtualRealityView::vrHomeWidget()const +{ + Q_D(const qMRMLVirtualRealityView); + return d->HomeWidget; +} + //------------------------------------------------------------------------------ void qMRMLVirtualRealityView::getDisplayableManagers(vtkCollection* displayableManagers) { @@ -815,3 +829,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 ac9f965..6e36771 100644 --- a/VirtualReality/Widgets/qMRMLVirtualRealityView.h +++ b/VirtualReality/Widgets/qMRMLVirtualRealityView.h @@ -33,6 +33,7 @@ // CTK includes #include +class qMRMLVirtualRealityHomeWidget; class qMRMLVirtualRealityViewPrivate; class vtkMRMLVirtualRealityViewNode; class vtkCollection; @@ -84,6 +85,8 @@ class Q_SLICER_QTMODULES_VIRTUALREALITY_WIDGETS_EXPORT qMRMLVirtualRealityView : /// Get the 3D View node observed by view. Q_INVOKABLE vtkMRMLVirtualRealityViewNode* mrmlVirtualRealityViewNode()const; + Q_INVOKABLE qMRMLVirtualRealityHomeWidget* vrHomeWidget()const; + /// Get a reference to the associated vtkRenderer vtkOpenVRRenderer* renderer()const; @@ -100,6 +103,8 @@ class Q_SLICER_QTMODULES_VIRTUALREALITY_WIDGETS_EXPORT qMRMLVirtualRealityView : /// Get underlying RenderWindow Q_INVOKABLE bool isHardwareConnected()const; + void registerModule(QWidget* widget, QIcon& icon); + signals: void physicalToWorldMatrixModified(); @@ -109,8 +114,10 @@ public slots: void onPhysicalToWorldMatrixModified(); -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 f109afc..26c81db 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