diff --git a/Modules/FormsUI/CMakeLists.txt b/Modules/FormsUI/CMakeLists.txt new file mode 100644 index 0000000000..2020d63fae --- /dev/null +++ b/Modules/FormsUI/CMakeLists.txt @@ -0,0 +1,9 @@ +mitk_create_module( + INCLUDE_DIRS + PUBLIC include + PRIVATE src + DEPENDS + PUBLIC MitkForms + PACKAGE_DEPENDS + PUBLIC Qt6|Widgets +) diff --git a/Modules/FormsUI/files.cmake b/Modules/FormsUI/files.cmake new file mode 100644 index 0000000000..3e2b067d18 --- /dev/null +++ b/Modules/FormsUI/files.cmake @@ -0,0 +1,40 @@ +set(H_FILES + include/mitkIQuestionWidgetFactory.h + include/QmitkCheckboxesQuestionWidget.h + include/QmitkDropdownQuestionWidget.h + include/QmitkForm.h + include/QmitkLinearScaleQuestionWidget.h + include/QmitkMultipleChoiceQuestionWidget.h + include/QmitkParagraphQuestionWidget.h + include/QmitkQuestionWidget.h + include/QmitkShortAnswerQuestionWidget.h +) + +set(MOC_H_FILES + include/QmitkCheckboxesQuestionWidget.h + include/QmitkDropdownQuestionWidget.h + include/QmitkLinearScaleQuestionWidget.h + include/QmitkForm.h + include/QmitkMultipleChoiceQuestionWidget.h + include/QmitkParagraphQuestionWidget.h + include/QmitkQuestionWidget.h + include/QmitkShortAnswerQuestionWidget.h +) + +set(UI_FILES + src/QmitkForm.ui +) + +set(CPP_FILES + mitkIQuestionWidgetFactory.cpp + mitkModuleActivator.cpp + mitkQuestionWidgetFactory.cpp + QmitkCheckboxesQuestionWidget.cpp + QmitkDropdownQuestionWidget.cpp + QmitkForm.cpp + QmitkLinearScaleQuestionWidget.cpp + QmitkMultipleChoiceQuestionWidget.cpp + QmitkParagraphQuestionWidget.cpp + QmitkQuestionWidget.cpp + QmitkShortAnswerQuestionWidget.cpp +) diff --git a/Modules/FormsUI/include/QmitkCheckboxesQuestionWidget.h b/Modules/FormsUI/include/QmitkCheckboxesQuestionWidget.h new file mode 100644 index 0000000000..65a3d214a7 --- /dev/null +++ b/Modules/FormsUI/include/QmitkCheckboxesQuestionWidget.h @@ -0,0 +1,52 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkCheckboxQuestionWidget_h +#define QmitkCheckboxQuestionWidget_h + +#include +#include + +class QButtonGroup; +class QGridLayout; +class QLineEdit; + +class MITKFORMSUI_EXPORT QmitkCheckboxesQuestionWidget : public QmitkQuestionWidget +{ + Q_OBJECT + +public: + explicit QmitkCheckboxesQuestionWidget(QWidget* parent = nullptr); + ~QmitkCheckboxesQuestionWidget() override; + + QmitkQuestionWidget* CreateAnother(QWidget* parent = nullptr) const override; + mitk::Forms::Question* GetQuestion() const override; + void SetQuestion(mitk::Forms::Question* question) override; + void Reset() override; + +private: + void CreateWidgets(); + void RemoveWidgets(); + + void OnIdToggled(int id, bool checked); + void OnTextEdited(const QString& text); + void OnEditingFinished(); + + mitk::Forms::CheckboxesQuestion* m_Question; + + QGridLayout* m_Layout; + QButtonGroup* m_ButtonGroup; + QLineEdit* m_OtherLineEdit; + int m_OtherId; +}; + +#endif diff --git a/Modules/FormsUI/include/QmitkDropdownQuestionWidget.h b/Modules/FormsUI/include/QmitkDropdownQuestionWidget.h new file mode 100644 index 0000000000..0814ac44df --- /dev/null +++ b/Modules/FormsUI/include/QmitkDropdownQuestionWidget.h @@ -0,0 +1,46 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkDropdownQuestionWidget_h +#define QmitkDropdownQuestionWidget_h + +#include +#include + +class QComboBox; + +class MITKFORMSUI_EXPORT QmitkDropdownQuestionWidget : public QmitkQuestionWidget +{ + Q_OBJECT + +public: + explicit QmitkDropdownQuestionWidget(QWidget* parent = nullptr); + ~QmitkDropdownQuestionWidget() override; + + QmitkQuestionWidget* CreateAnother(QWidget* parent = nullptr) const override; + mitk::Forms::Question* GetQuestion() const override; + void SetQuestion(mitk::Forms::Question* question) override; + void Reset() override; + +private: + void CreateWidgets(); + void RemoveWidgets(); + + void OnCurrentIndexChanged(int index); + + mitk::Forms::DropdownQuestion* m_Question; + + QVBoxLayout* m_Layout; + QComboBox* m_ComboBox; +}; + +#endif diff --git a/Modules/FormsUI/include/QmitkForm.h b/Modules/FormsUI/include/QmitkForm.h new file mode 100644 index 0000000000..52c777e3aa --- /dev/null +++ b/Modules/FormsUI/include/QmitkForm.h @@ -0,0 +1,62 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkForm_h +#define QmitkForm_h + +#include + +#include + +#include + +namespace Ui +{ + class QmitkForm; +} + +class MITKFORMSUI_EXPORT QmitkForm : public QWidget +{ + Q_OBJECT + +public: + explicit QmitkForm(mitk::Forms::Form& form, QWidget* parent = nullptr); + ~QmitkForm() override; + + fs::path GetResponsesPath() const; + void SetResponsesPath(const fs::path& csvPath); + +private: + void CreateQuestionWidgets(); + bool ValidateCurrentSection(); + void Reset(); + + void Update(); + void UpdateFormHeader(); + void UpdateSubmittedHeader(); + void UpdateSectionHeader(); + void UpdateQuestionWidgets(); + void UpdateFormButtons(); + + void OnBackButtonClicked(); + void OnNextButtonClicked(); + void OnSubmitButtonClicked(); + void OnClearButtonClicked(); + void OnSubmitAnotherButtonClicked(); + + Ui::QmitkForm* m_Ui; + mitk::Forms::Form& m_Form; + fs::path m_ResponsesPath; + bool m_HasBeenSubmitted; +}; + +#endif diff --git a/Modules/FormsUI/include/QmitkLinearScaleQuestionWidget.h b/Modules/FormsUI/include/QmitkLinearScaleQuestionWidget.h new file mode 100644 index 0000000000..5a14ff4d34 --- /dev/null +++ b/Modules/FormsUI/include/QmitkLinearScaleQuestionWidget.h @@ -0,0 +1,50 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkLinearScaleQuestionWidget_h +#define QmitkLinearScaleQuestionWidget_h + +#include +#include + +class QButtonGroup; +class QGridLayout; +class QPushButton; + +class MITKFORMSUI_EXPORT QmitkLinearScaleQuestionWidget : public QmitkQuestionWidget +{ + Q_OBJECT + +public: + explicit QmitkLinearScaleQuestionWidget(QWidget* parent = nullptr); + ~QmitkLinearScaleQuestionWidget() override; + + QmitkQuestionWidget* CreateAnother(QWidget* parent = nullptr) const override; + mitk::Forms::Question* GetQuestion() const override; + void SetQuestion(mitk::Forms::Question* question) override; + void Reset() override; + +private: + void CreateWidgets(); + void RemoveWidgets(); + + void OnIdClicked(int id); + void OnClearButtonClicked(); + + mitk::Forms::LinearScaleQuestion* m_Question; + + QGridLayout* m_Layout; + QButtonGroup* m_ButtonGroup; + QPushButton* m_ClearButton; +}; + +#endif diff --git a/Modules/FormsUI/include/QmitkMultipleChoiceQuestionWidget.h b/Modules/FormsUI/include/QmitkMultipleChoiceQuestionWidget.h new file mode 100644 index 0000000000..db0d58a697 --- /dev/null +++ b/Modules/FormsUI/include/QmitkMultipleChoiceQuestionWidget.h @@ -0,0 +1,55 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkMultipleChoiceQuestionWidget_h +#define QmitkMultipleChoiceQuestionWidget_h + +#include +#include + +class QButtonGroup; +class QGridLayout; +class QLineEdit; +class QPushButton; + +class MITKFORMSUI_EXPORT QmitkMultipleChoiceQuestionWidget : public QmitkQuestionWidget +{ + Q_OBJECT + +public: + explicit QmitkMultipleChoiceQuestionWidget(QWidget* parent = nullptr); + ~QmitkMultipleChoiceQuestionWidget() override; + + QmitkQuestionWidget* CreateAnother(QWidget* parent = nullptr) const override; + mitk::Forms::Question* GetQuestion() const override; + void SetQuestion(mitk::Forms::Question* question) override; + void Reset() override; + +private: + void CreateWidgets(); + void RemoveWidgets(); + + void OnIdClicked(int id); + void OnTextEdited(const QString& text); + void OnEditingFinished(); + void OnClearButtonClicked(); + + mitk::Forms::MultipleChoiceQuestion* m_Question; + + QGridLayout* m_Layout; + QButtonGroup* m_ButtonGroup; + QLineEdit* m_OtherLineEdit; + int m_OtherId; + QPushButton* m_ClearButton; +}; + +#endif diff --git a/Modules/FormsUI/include/QmitkParagraphQuestionWidget.h b/Modules/FormsUI/include/QmitkParagraphQuestionWidget.h new file mode 100644 index 0000000000..23590d541f --- /dev/null +++ b/Modules/FormsUI/include/QmitkParagraphQuestionWidget.h @@ -0,0 +1,44 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkParagraphQuestionWidget_h +#define QmitkParagraphQuestionWidget_h + +#include +#include + +class QTextEdit; + +class MITKFORMSUI_EXPORT QmitkParagraphQuestionWidget : public QmitkQuestionWidget +{ + Q_OBJECT + +public: + explicit QmitkParagraphQuestionWidget(QWidget* parent = nullptr); + ~QmitkParagraphQuestionWidget() override; + + QmitkQuestionWidget* CreateAnother(QWidget* parent = nullptr) const override; + mitk::Forms::Question* GetQuestion() const override; + void SetQuestion(mitk::Forms::Question* question) override; + void Reset() override; + +private: + void AdjustHeight(); + void OnTextChanged(); + + mitk::Forms::ParagraphQuestion* m_Question; + + QVBoxLayout* m_Layout; + QTextEdit* m_TextEdit; +}; + +#endif diff --git a/Modules/FormsUI/include/QmitkQuestionWidget.h b/Modules/FormsUI/include/QmitkQuestionWidget.h new file mode 100644 index 0000000000..2491417ab7 --- /dev/null +++ b/Modules/FormsUI/include/QmitkQuestionWidget.h @@ -0,0 +1,53 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkQuestionWidget_h +#define QmitkQuestionWidget_h + +#include +#include + +class QVBoxLayout; +class QLabel; + +namespace mitk::Forms +{ + class Question; +} + +class MITKFORMSUI_EXPORT QmitkQuestionWidget : public QFrame +{ + Q_OBJECT + +public: + explicit QmitkQuestionWidget(QWidget* parent = nullptr); + ~QmitkQuestionWidget() override; + + virtual QmitkQuestionWidget* CreateAnother(QWidget* parent = nullptr) const = 0; + virtual mitk::Forms::Question* GetQuestion() const = 0; + virtual void SetQuestion(mitk::Forms::Question* question) = 0; + virtual void Reset() = 0; + + void SetRequirementVisible(bool visible); + void ShowRequirement(); + void HideRequirement(); + +protected: + void InsertLayout(QLayout* layout); + +private: + QVBoxLayout* m_Layout; + QLabel* m_QuestionLabel; + QLabel* m_RequiredLabel; +}; + +#endif diff --git a/Modules/FormsUI/include/QmitkShortAnswerQuestionWidget.h b/Modules/FormsUI/include/QmitkShortAnswerQuestionWidget.h new file mode 100644 index 0000000000..a62359d6ee --- /dev/null +++ b/Modules/FormsUI/include/QmitkShortAnswerQuestionWidget.h @@ -0,0 +1,43 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkShortAnswerQuestionWidget_h +#define QmitkShortAnswerQuestionWidget_h + +#include +#include + +class QLineEdit; + +class MITKFORMSUI_EXPORT QmitkShortAnswerQuestionWidget : public QmitkQuestionWidget +{ + Q_OBJECT + +public: + explicit QmitkShortAnswerQuestionWidget(QWidget* parent = nullptr); + ~QmitkShortAnswerQuestionWidget() override; + + QmitkQuestionWidget* CreateAnother(QWidget* parent = nullptr) const override; + mitk::Forms::Question* GetQuestion() const override; + void SetQuestion(mitk::Forms::Question* question) override; + void Reset() override; + +private: + void OnTextEdited(const QString& text); + + mitk::Forms::ShortAnswerQuestion* m_Question; + + QVBoxLayout* m_Layout; + QLineEdit* m_LineEdit; +}; + +#endif diff --git a/Modules/FormsUI/include/mitkIQuestionWidgetFactory.h b/Modules/FormsUI/include/mitkIQuestionWidgetFactory.h new file mode 100644 index 0000000000..e5f86d6659 --- /dev/null +++ b/Modules/FormsUI/include/mitkIQuestionWidgetFactory.h @@ -0,0 +1,42 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkIQuestionWidgetFactory_h +#define mitkIQuestionWidgetFactory_h + +#include +#include + +#include + +namespace mitk::Forms +{ + class Question; + + namespace UI + { + class MITKFORMSUI_EXPORT IQuestionWidgetFactory + { + public: + static IQuestionWidgetFactory* GetInstance(); + + virtual ~IQuestionWidgetFactory(); + + virtual void Register(const std::string& questionType, QmitkQuestionWidget* widgetPrototype) = 0; + virtual QmitkQuestionWidget* Create(Question* question, QWidget* parent = nullptr) const = 0; + }; + } +} + +MITK_DECLARE_SERVICE_INTERFACE(mitk::Forms::UI::IQuestionWidgetFactory, "org.mitk.Forms.UI.IQuestionWidgetFactory") + +#endif diff --git a/Modules/FormsUI/src/QmitkCheckboxesQuestionWidget.cpp b/Modules/FormsUI/src/QmitkCheckboxesQuestionWidget.cpp new file mode 100644 index 0000000000..81a80f3a37 --- /dev/null +++ b/Modules/FormsUI/src/QmitkCheckboxesQuestionWidget.cpp @@ -0,0 +1,172 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include +#include + +#include +#include +#include +#include + +using namespace mitk::Forms; +using Self = QmitkCheckboxesQuestionWidget; + +QmitkCheckboxesQuestionWidget::QmitkCheckboxesQuestionWidget(QWidget* parent) + : QmitkQuestionWidget(parent), + m_Question(nullptr), + m_Layout(new QGridLayout), + m_ButtonGroup(nullptr), + m_OtherLineEdit(nullptr), + m_OtherId(-1) +{ + this->InsertLayout(m_Layout); +} + +QmitkCheckboxesQuestionWidget::~QmitkCheckboxesQuestionWidget() +{ +} + +QmitkQuestionWidget* QmitkCheckboxesQuestionWidget::CreateAnother(QWidget* parent) const +{ + return new QmitkCheckboxesQuestionWidget(parent); +} + +Question* QmitkCheckboxesQuestionWidget::GetQuestion() const +{ + return m_Question; +} + +void QmitkCheckboxesQuestionWidget::SetQuestion(Question* question) +{ + QmitkQuestionWidget::SetQuestion(question); + + auto checkboxesQuestion = dynamic_cast(question); + + if (checkboxesQuestion == nullptr) + mitkThrow() << "QmitkCheckboxesQuestionWidget only accepts CheckboxesQuestion as question type!"; + + m_Question = checkboxesQuestion; + + this->CreateWidgets(); +} + +void QmitkCheckboxesQuestionWidget::Reset() +{ + if (m_ButtonGroup != nullptr) + { + for (auto checkBox : m_ButtonGroup->buttons()) + checkBox->setChecked(false); + } + + if (m_OtherLineEdit != nullptr) + m_OtherLineEdit->clear(); + + if (m_Question != nullptr) + m_Question->ClearResponses(); + + QmitkQuestionWidget::Reset(); +} + +void QmitkCheckboxesQuestionWidget::CreateWidgets() +{ + this->RemoveWidgets(); + + int row = 0; + + for (const auto& option : m_Question->GetOptions()) + { + auto checkBox = new QCheckBox(QString::fromStdString(option)); + m_Layout->addWidget(checkBox, row, 0, 1, 2); + m_ButtonGroup->addButton(checkBox, row++); + } + + if (m_Question->HasOtherOption()) + { + auto checkBox = new QCheckBox("Other:"); + m_Layout->addWidget(checkBox, row, 0); + m_ButtonGroup->addButton(checkBox, row); + m_OtherId = row; + + m_OtherLineEdit = new QLineEdit; + m_Layout->addWidget(m_OtherLineEdit, row++, 1); + + connect(m_OtherLineEdit, &QLineEdit::textEdited, this, &Self::OnTextEdited); + connect(m_OtherLineEdit, &QLineEdit::editingFinished, this, &Self::OnEditingFinished); + } + else + { + m_OtherId = -1; + } +} + +void QmitkCheckboxesQuestionWidget::RemoveWidgets() +{ + if (m_ButtonGroup != nullptr) + delete m_ButtonGroup; + + m_ButtonGroup = new QButtonGroup(this); + m_ButtonGroup->setExclusive(false); + + connect(m_ButtonGroup, &QButtonGroup::idToggled, this, &Self::OnIdToggled); + + QLayoutItem* child; + while ((child = m_Layout->takeAt(0)) != nullptr) + { + delete child->widget(); + delete child; + } + + m_OtherLineEdit = nullptr; +} + +void QmitkCheckboxesQuestionWidget::OnIdToggled(int id, bool checked) +{ + if (checked) + { + if (id == m_OtherId) + { + m_Question->AddOtherResponse(m_OtherLineEdit->text().toStdString()); + m_OtherLineEdit->setFocus(); + } + else + { + m_Question->AddResponse(static_cast(id)); + } + } + else + { + if (id == m_OtherId) + { + m_Question->RemoveOtherResponse(); + } + else + { + m_Question->RemoveResponse(static_cast(id)); + } + } + + if (m_Question->IsRequired()) + this->SetRequirementVisible(!m_Question->IsComplete()); +} + +void QmitkCheckboxesQuestionWidget::OnTextEdited(const QString&) +{ + if (!m_ButtonGroup->button(m_OtherId)->isChecked()) + m_ButtonGroup->button(m_OtherId)->click(); +} + +void QmitkCheckboxesQuestionWidget::OnEditingFinished() +{ + if (m_ButtonGroup->button(m_OtherId)->isChecked()) + m_Question->AddOtherResponse(m_OtherLineEdit->text().toStdString()); +} diff --git a/Modules/FormsUI/src/QmitkDropdownQuestionWidget.cpp b/Modules/FormsUI/src/QmitkDropdownQuestionWidget.cpp new file mode 100644 index 0000000000..91f6041b95 --- /dev/null +++ b/Modules/FormsUI/src/QmitkDropdownQuestionWidget.cpp @@ -0,0 +1,104 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include +#include + +#include +#include + +using namespace mitk::Forms; +using Self = QmitkDropdownQuestionWidget; + +QmitkDropdownQuestionWidget::QmitkDropdownQuestionWidget(QWidget* parent) + : QmitkQuestionWidget(parent), + m_Question(nullptr), + m_Layout(new QVBoxLayout), + m_ComboBox(new QComboBox) +{ + m_Layout->addWidget(m_ComboBox); + + this->InsertLayout(m_Layout); + + connect(m_ComboBox, &QComboBox::currentIndexChanged, this, &Self::OnCurrentIndexChanged); +} + +QmitkDropdownQuestionWidget::~QmitkDropdownQuestionWidget() +{ +} + +QmitkQuestionWidget* QmitkDropdownQuestionWidget::CreateAnother(QWidget* parent) const +{ + return new QmitkDropdownQuestionWidget(parent); +} + +Question* QmitkDropdownQuestionWidget::GetQuestion() const +{ + return m_Question; +} + +void QmitkDropdownQuestionWidget::SetQuestion(Question* question) +{ + QmitkQuestionWidget::SetQuestion(question); + + auto dropdownQuestion = dynamic_cast(question); + + if (dropdownQuestion == nullptr) + mitkThrow() << "QmitkDropdownQuestionWidget only accepts DropdownQuestion as question type!"; + + m_Question = dropdownQuestion; + + this->CreateWidgets(); +} + +void QmitkDropdownQuestionWidget::Reset() +{ + m_ComboBox->setCurrentIndex(0); + + if (m_Question) + m_Question->ClearResponses(); + + QmitkQuestionWidget::Reset(); +} + +void QmitkDropdownQuestionWidget::CreateWidgets() +{ + this->RemoveWidgets(); + + for (const auto& option : m_Question->GetOptions()) + m_ComboBox->addItem(QString::fromStdString(option)); +} + +void QmitkDropdownQuestionWidget::RemoveWidgets() +{ + m_ComboBox->clear(); + m_ComboBox->addItem("Choose"); + m_ComboBox->insertSeparator(1); +} + +void QmitkDropdownQuestionWidget::OnCurrentIndexChanged(int index) +{ + if (m_Question == nullptr) + return; + + if (index > 1) + { + m_Question->SetResponse(static_cast(index - 2)); + } + else + { + m_Question->ClearResponses(); + } + + if (m_Question->IsRequired()) + this->SetRequirementVisible(!m_Question->IsComplete()); +} diff --git a/Modules/FormsUI/src/QmitkForm.cpp b/Modules/FormsUI/src/QmitkForm.cpp new file mode 100644 index 0000000000..7c6a170dea --- /dev/null +++ b/Modules/FormsUI/src/QmitkForm.cpp @@ -0,0 +1,333 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace mitk::Forms; +using Self = QmitkForm; + +QmitkForm::QmitkForm(Form& form, QWidget* parent) + : QWidget(parent), + m_Ui(new Ui::QmitkForm), + m_Form(form), + m_HasBeenSubmitted(false) +{ + this->setStyleSheet( + "QFrame[frameShape=\"1\"] { border-radius: 6px; }" + "QComboBox, QComboBox::drop-down, QLineEdit, QPushButton { border-radius: 4px; }" + ); + + m_Ui->setupUi(this); + + this->CreateQuestionWidgets(); + this->Update(); + + connect(m_Ui->sectionWidget, &QStackedWidget::currentChanged, this, [this](int) { this->Update(); }); + + connect(m_Ui->backButton, &QPushButton::clicked, this, &Self::OnBackButtonClicked); + connect(m_Ui->nextButton, &QPushButton::clicked, this, &Self::OnNextButtonClicked); + connect(m_Ui->submitButton, &QPushButton::clicked, this, &Self::OnSubmitButtonClicked); + connect(m_Ui->clearButton, &QPushButton::clicked, this, &Self::OnClearButtonClicked); + connect(m_Ui->submitAnotherButton, &QPushButton::clicked, this, &Self::OnSubmitAnotherButtonClicked); +} + +QmitkForm::~QmitkForm() +{ +} + +fs::path QmitkForm::GetResponsesPath() const +{ + return m_ResponsesPath; +} + +void QmitkForm::SetResponsesPath(const fs::path& csvPath) +{ + m_ResponsesPath = csvPath; +} + +void QmitkForm::CreateQuestionWidgets() +{ + const int numberOfSections = m_Form.GetNumberOfSections(); + + for (int sectionIndex = 0; sectionIndex < numberOfSections; ++sectionIndex) + { + const auto& section = m_Form.GetSection(sectionIndex); + auto questions = section.GetQuestions(); + + auto sectionWidget = new QWidget; + m_Ui->sectionWidget->addWidget(sectionWidget); + + auto sectionLayout = new QVBoxLayout(sectionWidget); + sectionLayout->setContentsMargins(0, 0, 0, 0); + sectionLayout->setSpacing(8); + + for (auto question : questions) + { + auto questionWidget = UI::IQuestionWidgetFactory::GetInstance()->Create(question); + sectionLayout->addWidget(questionWidget); + } + } +} + +bool QmitkForm::ValidateCurrentSection() +{ + auto sectionWidget = m_Ui->sectionWidget->currentWidget(); + auto questionWidgets = sectionWidget->findChildren(); + + bool isComplete = true; + + for (auto questionWidget : questionWidgets) + { + auto question = questionWidget->GetQuestion(); + + if (question->IsRequired() && !question->IsComplete()) + { + questionWidget->ShowRequirement(); + isComplete = false; + } + } + + return isComplete; +} + +void QmitkForm::Reset() +{ + int numberOfSections = m_Ui->sectionWidget->count(); + + for (int sectionIndex = 0; sectionIndex < numberOfSections; ++sectionIndex) + { + auto sectionWidget = m_Ui->sectionWidget->widget(sectionIndex); + auto questionWidgets = sectionWidget->findChildren(); + + for (auto questionWidget : questionWidgets) + questionWidget->Reset(); + } + + m_HasBeenSubmitted = false; + m_Ui->sectionWidget->setCurrentIndex(0); +} + +void QmitkForm::Update() +{ + this->UpdateFormHeader(); + this->UpdateSubmittedHeader(); + this->UpdateSectionHeader(); + this->UpdateQuestionWidgets(); + this->UpdateFormButtons(); +} + +void QmitkForm::UpdateFormHeader() +{ + if (m_HasBeenSubmitted) + { + m_Ui->formHeaderFrame->hide(); + return; + } + + bool showTitle = !m_Form.GetTitle().empty(); + + m_Ui->formTitleLabel->setVisible(showTitle); + + if (showTitle) + m_Ui->formTitleLabel->setText(QString::fromStdString(m_Form.GetTitle())); + + int sectionIndex = m_Ui->sectionWidget->currentIndex(); + bool showDescription = sectionIndex == 0 && !m_Form.GetDescription().empty(); + + m_Ui->formDescriptionLabel->setVisible(showDescription); + + if (showDescription) + m_Ui->formDescriptionLabel->setText(QString::fromStdString(m_Form.GetDescription())); + + bool hasRequiredQuestion = false; + + for (auto question : m_Form.GetSection(sectionIndex).GetQuestions()) + { + if (question->IsRequired()) + { + hasRequiredQuestion = true; + break; + } + } + + m_Ui->requiredLabel->setVisible(hasRequiredQuestion); + + m_Ui->formHeaderFrame->setVisible(showTitle || showDescription || hasRequiredQuestion); +} + +void QmitkForm::UpdateSubmittedHeader() +{ + if (m_HasBeenSubmitted) + { + bool showTitle = !m_Form.GetTitle().empty(); + + m_Ui->submittedTitleLabel->setVisible(showTitle); + + if (showTitle) + m_Ui->submittedTitleLabel->setText(QString::fromStdString(m_Form.GetTitle())); + + m_Ui->submittedFrame->show(); + } + else + { + m_Ui->submittedFrame->hide(); + } +} + +void QmitkForm::UpdateSectionHeader() +{ + if (m_HasBeenSubmitted) + { + m_Ui->sectionHeaderFrame->hide(); + return; + } + + int sectionIndex = m_Ui->sectionWidget->currentIndex(); + + if (sectionIndex == 0) + { + m_Ui->sectionHeaderFrame->hide(); + return; + } + + const auto& section = m_Form.GetSection(sectionIndex); + bool showTitle = !section.GetTitle().empty(); + + m_Ui->sectionTitleLabel->setVisible(showTitle); + + if (showTitle) + m_Ui->sectionTitleLabel->setText(QString::fromStdString(section.GetTitle())); + + bool showDescription = !section.GetDescription().empty(); + + m_Ui->sectionDescriptionLabel->setVisible(showDescription); + + if (showDescription) + m_Ui->sectionDescriptionLabel->setText(QString::fromStdString(section.GetDescription())); + + m_Ui->sectionHeaderFrame->setVisible(showTitle || showDescription); +} + +void QmitkForm::UpdateQuestionWidgets() +{ + if (m_HasBeenSubmitted) + { + m_Ui->sectionWidget->hide(); + return; + } + else + { + m_Ui->sectionWidget->show(); + } + + int currentSectionIndex = m_Ui->sectionWidget->currentIndex(); + int numberOfSections = m_Ui->sectionWidget->count(); + + for (int sectionIndex = 0; sectionIndex < numberOfSections; ++sectionIndex) + { + auto sectionWidget = m_Ui->sectionWidget->widget(sectionIndex); + auto questionWidgets = sectionWidget->findChildren(); + + for (auto questionWidget : questionWidgets) + questionWidget->setVisible(sectionIndex == currentSectionIndex); + } +} + +void QmitkForm::UpdateFormButtons() +{ + int sectionIndex = m_Ui->sectionWidget->currentIndex(); + + m_Ui->backButton->setVisible(!m_HasBeenSubmitted && sectionIndex != 0); + m_Ui->nextButton->setVisible(!m_HasBeenSubmitted && sectionIndex < m_Ui->sectionWidget->count() - 1); + m_Ui->submitButton->setVisible(!m_HasBeenSubmitted && sectionIndex == m_Ui->sectionWidget->count() - 1); + m_Ui->clearButton->setVisible(!m_HasBeenSubmitted); +} + +void QmitkForm::OnBackButtonClicked() +{ + m_Ui->sectionWidget->setCurrentIndex(m_Ui->sectionWidget->currentIndex() - 1); +} + +void QmitkForm::OnNextButtonClicked() +{ + if (this->ValidateCurrentSection()) + m_Ui->sectionWidget->setCurrentIndex(m_Ui->sectionWidget->currentIndex() + 1); +} + +void QmitkForm::OnSubmitButtonClicked() +{ + if (this->ValidateCurrentSection()) + { + if (m_ResponsesPath.empty()) + { + m_ResponsesPath = QFileDialog::getSaveFileName(this, + "Submit Form", + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).append("/form.csv"), + "Comma-separated values (*.csv)").toStdString(); + } + + if (!m_ResponsesPath.empty()) + { + bool retry; + + do + { + retry = false; + + try + { + m_Form.Submit(m_ResponsesPath); + } + catch (const mitk::Exception& e) + { + int buttonId = QMessageBox::critical(this, + "Submit form", + e.GetDescription(), + "Retry", // Id: 2 + "Ignore"); // Id: 3 + + if (buttonId == 2) + retry = true; + } + } while (retry); + + m_HasBeenSubmitted = true; + this->Update(); + } + } +} + +void QmitkForm::OnClearButtonClicked() +{ + int buttonId = QMessageBox::question(this, + "Clear form?", + "This will remove your answers from all\nquestions and cannot be undone.", + "Cancel", // Id: 2 + "Clear form"); // Id: 3 + + if (buttonId == 3) + this->Reset(); +} + +void QmitkForm::OnSubmitAnotherButtonClicked() +{ + this->Reset(); +} diff --git a/Modules/FormsUI/src/QmitkForm.ui b/Modules/FormsUI/src/QmitkForm.ui new file mode 100644 index 0000000000..8b38ff5419 --- /dev/null +++ b/Modules/FormsUI/src/QmitkForm.ui @@ -0,0 +1,247 @@ + + + QmitkForm + + + + 0 + 0 + 600 + 313 + + + + + 0 + 0 + + + + + 8 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + 16 + + + + Form Title + + + true + + + + + + + Form description. + + + true + + + + + + + <span style="color: red;">* Indicates required question</span> + + + true + + + + + + + + + + QFrame::Box + + + QFrame::Plain + + + + + + + 16 + + + + Form Title + + + true + + + + + + + Your response has been recorded. + + + true + + + + + + + Submit another response + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + 0 + + + + QFrame::Box + + + + + + + 12 + + + + Section Title + + + true + + + + + + + Section description. + + + true + + + + + + + + + + + 0 + 0 + + + + + + + + QLayout::SetMaximumSize + + + + + Back + + + + + + + Next + + + + + + + Submit + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Clear form + + + true + + + + + + + + + + diff --git a/Modules/FormsUI/src/QmitkLinearScaleQuestionWidget.cpp b/Modules/FormsUI/src/QmitkLinearScaleQuestionWidget.cpp new file mode 100644 index 0000000000..2a557fc2f7 --- /dev/null +++ b/Modules/FormsUI/src/QmitkLinearScaleQuestionWidget.cpp @@ -0,0 +1,161 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include +#include + +#include +#include +#include +#include +#include + +using namespace mitk::Forms; +using Self = QmitkLinearScaleQuestionWidget; + +QmitkLinearScaleQuestionWidget::QmitkLinearScaleQuestionWidget(QWidget* parent) + : QmitkQuestionWidget(parent), + m_Question(nullptr), + m_Layout(new QGridLayout), + m_ButtonGroup(nullptr), + m_ClearButton(nullptr) +{ + this->InsertLayout(m_Layout); +} + +QmitkLinearScaleQuestionWidget::~QmitkLinearScaleQuestionWidget() +{ +} + +QmitkQuestionWidget* QmitkLinearScaleQuestionWidget::CreateAnother(QWidget* parent) const +{ + return new QmitkLinearScaleQuestionWidget(parent); +} + +Question* QmitkLinearScaleQuestionWidget::GetQuestion() const +{ + return m_Question; +} + +void QmitkLinearScaleQuestionWidget::SetQuestion(Question* question) +{ + QmitkQuestionWidget::SetQuestion(question); + + auto linearScaleQuestion = dynamic_cast(question); + + if (linearScaleQuestion == nullptr) + mitkThrow() << "QmitkLinearScaleQuestionWidget only accepts LinearScaleQuestion as question type!"; + + m_Question = linearScaleQuestion; + + this->CreateWidgets(); +} + +void QmitkLinearScaleQuestionWidget::Reset() +{ + this->OnClearButtonClicked(); + + if (m_Question) + m_Question->ClearResponses(); + + QmitkQuestionWidget::Reset(); +} + +void QmitkLinearScaleQuestionWidget::CreateWidgets() +{ + this->RemoveWidgets(); + + auto [from, to] = m_Question->GetRange(); + auto [fromText, toText] = m_Question->GetRangeLabels(); + int column = 0; + + if (!fromText.empty()) + { + auto fromLabel = new QLabel(QString::fromStdString(fromText)); + m_Layout->addWidget(fromLabel, 1, column++, Qt::AlignRight); + } + + for (int i = from; i <= to; ++i) + { + auto label = new QLabel(QString("%1").arg(i)); + + auto radioButton = new QRadioButton; + radioButton->setFixedWidth(radioButton->sizeHint().height()); + + m_Layout->addWidget(label, 0, column, Qt::AlignHCenter); + m_Layout->addWidget(radioButton, 1, column++, Qt::AlignHCenter); + + m_ButtonGroup->addButton(radioButton, i); + } + + if (!toText.empty()) + { + auto toLabel = new QLabel(QString::fromStdString(toText)); + m_Layout->addWidget(toLabel, 1, column++, Qt::AlignLeft); + } + + if (m_Question->IsRequired()) + return; + + m_ClearButton = new QPushButton("Clear selection"); + m_ClearButton->setFlat(true); + m_ClearButton->hide(); + + m_Layout->addWidget(m_ClearButton, 2, 0, 1, column, Qt::AlignRight); + + connect(m_ClearButton, &QPushButton::clicked, this, &Self::OnClearButtonClicked); +} + +void QmitkLinearScaleQuestionWidget::RemoveWidgets() +{ + if (m_ButtonGroup != nullptr) + delete m_ButtonGroup; + + m_ButtonGroup = new QButtonGroup(this); + + connect(m_ButtonGroup, &QButtonGroup::idClicked, this, &Self::OnIdClicked); + + QLayoutItem* child; + while ((child = m_Layout->takeAt(0)) != nullptr) + { + delete child->widget(); + delete child; + } + + m_ClearButton = nullptr; +} + +void QmitkLinearScaleQuestionWidget::OnIdClicked(int id) +{ + m_Question->SetResponse(id); + + if (m_ClearButton != nullptr) + m_ClearButton->show(); + + if (m_Question->IsRequired()) + this->SetRequirementVisible(!m_Question->IsComplete()); +} + +void QmitkLinearScaleQuestionWidget::OnClearButtonClicked() +{ + auto checkedButton = m_ButtonGroup->checkedButton(); + + if (checkedButton != nullptr) + { + m_ButtonGroup->setExclusive(false); + checkedButton->setChecked(false); + m_ButtonGroup->setExclusive(true); + } + + if (m_ClearButton != nullptr) + m_ClearButton->hide(); +} diff --git a/Modules/FormsUI/src/QmitkMultipleChoiceQuestionWidget.cpp b/Modules/FormsUI/src/QmitkMultipleChoiceQuestionWidget.cpp new file mode 100644 index 0000000000..0c74854685 --- /dev/null +++ b/Modules/FormsUI/src/QmitkMultipleChoiceQuestionWidget.cpp @@ -0,0 +1,185 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include +#include + +#include +#include +#include +#include +#include + +using namespace mitk::Forms; +using Self = QmitkMultipleChoiceQuestionWidget; + +QmitkMultipleChoiceQuestionWidget::QmitkMultipleChoiceQuestionWidget(QWidget* parent) + : QmitkQuestionWidget(parent), + m_Question(nullptr), + m_Layout(new QGridLayout), + m_ButtonGroup(nullptr), + m_OtherLineEdit(nullptr), + m_OtherId(-1), + m_ClearButton(nullptr) +{ + this->InsertLayout(m_Layout); +} + +QmitkMultipleChoiceQuestionWidget::~QmitkMultipleChoiceQuestionWidget() +{ +} + +QmitkQuestionWidget* QmitkMultipleChoiceQuestionWidget::CreateAnother(QWidget* parent) const +{ + return new QmitkMultipleChoiceQuestionWidget(parent); +} + +Question* QmitkMultipleChoiceQuestionWidget::GetQuestion() const +{ + return m_Question; +} + +void QmitkMultipleChoiceQuestionWidget::SetQuestion(Question* question) +{ + QmitkQuestionWidget::SetQuestion(question); + + auto multipleChoiceQuestion = dynamic_cast(question); + + if (multipleChoiceQuestion == nullptr) + mitkThrow() << "QmitkMultipleChoiceQuestionWidget only accepts MultipleChoiceQuestion as question type!"; + + m_Question = multipleChoiceQuestion; + + this->CreateWidgets(); +} + +void QmitkMultipleChoiceQuestionWidget::Reset() +{ + this->OnClearButtonClicked(); + + if (m_OtherLineEdit != nullptr) + m_OtherLineEdit->clear(); + + if (m_Question != nullptr) + m_Question->ClearResponses(); + + QmitkQuestionWidget::Reset(); +} + +void QmitkMultipleChoiceQuestionWidget::CreateWidgets() +{ + this->RemoveWidgets(); + + int row = 0; + + for (const auto& option : m_Question->GetOptions()) + { + auto optionRadioButton = new QRadioButton(QString::fromStdString(option)); + m_Layout->addWidget(optionRadioButton, row, 0, 1, 2); + m_ButtonGroup->addButton(optionRadioButton, row++); + } + + if (m_Question->HasOtherOption()) + { + auto otherRadioButton = new QRadioButton("Other:"); + m_Layout->addWidget(otherRadioButton, row, 0); + m_ButtonGroup->addButton(otherRadioButton, row); + m_OtherId = row; + + m_OtherLineEdit = new QLineEdit; + m_Layout->addWidget(m_OtherLineEdit, row++, 1); + + connect(m_OtherLineEdit, &QLineEdit::textEdited, this, &Self::OnTextEdited); + connect(m_OtherLineEdit, &QLineEdit::editingFinished, this, &Self::OnEditingFinished); + } + else + { + m_OtherId = -1; + } + + if (m_Question->IsRequired()) + return; + + m_ClearButton = new QPushButton("Clear selection"); + m_ClearButton->setFlat(true); + m_ClearButton->hide(); + + m_Layout->addWidget(m_ClearButton, row, 0, 1, 2, Qt::AlignRight); + + connect(m_ClearButton, &QPushButton::clicked, this, &Self::OnClearButtonClicked); +} + +void QmitkMultipleChoiceQuestionWidget::RemoveWidgets() +{ + if (m_ButtonGroup != nullptr) + delete m_ButtonGroup; + + m_ButtonGroup = new QButtonGroup(this); + + connect(m_ButtonGroup, &QButtonGroup::idClicked, this, &Self::OnIdClicked); + + QLayoutItem* child; + while ((child = m_Layout->takeAt(0)) != nullptr) + { + delete child->widget(); + delete child; + } + + m_OtherLineEdit = nullptr; + m_ClearButton = nullptr; +} + +void QmitkMultipleChoiceQuestionWidget::OnIdClicked(int id) +{ + if (id == m_OtherId) + { + m_Question->SetOtherResponse(m_OtherLineEdit->text().toStdString()); + m_OtherLineEdit->setFocus(); + } + else + { + m_Question->SetResponse(static_cast(id)); + } + + if (m_ClearButton != nullptr) + m_ClearButton->show(); + + if (m_Question->IsRequired()) + this->SetRequirementVisible(!m_Question->IsComplete()); +} + +void QmitkMultipleChoiceQuestionWidget::OnTextEdited(const QString&) +{ + if (m_ButtonGroup->checkedId() != m_OtherId) + m_ButtonGroup->button(m_OtherId)->click(); +} + +void QmitkMultipleChoiceQuestionWidget::OnEditingFinished() +{ + if (m_ButtonGroup->checkedId() == m_OtherId) + m_Question->SetOtherResponse(m_OtherLineEdit->text().toStdString()); +} + +void QmitkMultipleChoiceQuestionWidget::OnClearButtonClicked() +{ + auto checkedButton = m_ButtonGroup->checkedButton(); + + if (checkedButton != nullptr) + { + m_ButtonGroup->setExclusive(false); + checkedButton->setChecked(false); + m_ButtonGroup->setExclusive(true); + } + + if (m_ClearButton != nullptr) + m_ClearButton->hide(); +} diff --git a/Modules/FormsUI/src/QmitkParagraphQuestionWidget.cpp b/Modules/FormsUI/src/QmitkParagraphQuestionWidget.cpp new file mode 100644 index 0000000000..5a2e1778f3 --- /dev/null +++ b/Modules/FormsUI/src/QmitkParagraphQuestionWidget.cpp @@ -0,0 +1,91 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include +#include + +#include +#include +#include + +using namespace mitk::Forms; +using Self = QmitkParagraphQuestionWidget; + +QmitkParagraphQuestionWidget::QmitkParagraphQuestionWidget(QWidget* parent) + : QmitkQuestionWidget(parent), + m_Question(nullptr), + m_Layout(new QVBoxLayout), + m_TextEdit(new QTextEdit) +{ + m_TextEdit->setPlaceholderText("Your answer"); + m_Layout->addWidget(m_TextEdit); + + this->InsertLayout(m_Layout); + this->AdjustHeight(); + + connect(m_TextEdit, &QTextEdit::textChanged, this, &Self::OnTextChanged); +} + +QmitkParagraphQuestionWidget::~QmitkParagraphQuestionWidget() +{ +} + +QmitkQuestionWidget* QmitkParagraphQuestionWidget::CreateAnother(QWidget* parent) const +{ + return new QmitkParagraphQuestionWidget(parent); +} + +Question* QmitkParagraphQuestionWidget::GetQuestion() const +{ + return m_Question; +} + +void QmitkParagraphQuestionWidget::SetQuestion(Question* question) +{ + QmitkQuestionWidget::SetQuestion(question); + + auto paragraphQuestion = dynamic_cast(question); + + if (paragraphQuestion == nullptr) + mitkThrow() << "QmitkParagraphQuestionWidget only accepts ParagraphQuestion as question type!"; + + m_Question = paragraphQuestion; +} + +void QmitkParagraphQuestionWidget::Reset() +{ + m_TextEdit->clear(); + + if (m_Question) + m_Question->ClearResponses(); + + QmitkQuestionWidget::Reset(); +} + +void QmitkParagraphQuestionWidget::AdjustHeight() +{ + QMargins contentsMargins = m_TextEdit->contentsMargins(); + int documentHeight = static_cast(m_TextEdit->document()->documentLayout()->documentSize().height()); + int totalHeight = contentsMargins.top() + documentHeight + contentsMargins.bottom(); + + m_TextEdit->setFixedHeight(totalHeight); +} + +void QmitkParagraphQuestionWidget::OnTextChanged() +{ + m_Question->SetResponse(m_TextEdit->toPlainText().toStdString()); + + if (m_Question->IsRequired()) + this->SetRequirementVisible(!m_Question->IsComplete()); + + this->AdjustHeight(); +} diff --git a/Modules/FormsUI/src/QmitkQuestionWidget.cpp b/Modules/FormsUI/src/QmitkQuestionWidget.cpp new file mode 100644 index 0000000000..40f0e30ead --- /dev/null +++ b/Modules/FormsUI/src/QmitkQuestionWidget.cpp @@ -0,0 +1,94 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include + +#include +#include + +#include +#include + +QmitkQuestionWidget::QmitkQuestionWidget(QWidget* parent) + : QFrame(parent), + m_Layout(new QVBoxLayout(this)), + m_QuestionLabel(new QLabel), + m_RequiredLabel(new QLabel) +{ + this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + this->setFrameShape(QFrame::Box); + + m_Layout->addWidget(m_QuestionLabel); + m_Layout->addWidget(m_RequiredLabel); + + m_QuestionLabel->setWordWrap(true); + + m_RequiredLabel->setWordWrap(true); + m_RequiredLabel->hide(); +} + +QmitkQuestionWidget::~QmitkQuestionWidget() +{ +} + +void QmitkQuestionWidget::SetQuestion(mitk::Forms::Question* question) +{ + if (question == nullptr) + mitkThrow() << "Invalid question: cannot be nullptr!"; + + auto text = QString::fromStdString(question->GetQuestionText()); + + if (question->IsRequired()) + { + if (!text.isEmpty()) + text.append(' '); + + text.append("*"); + } + + m_QuestionLabel->setText(text); + m_RequiredLabel->setText(QString("%1").arg(QString::fromStdString(question->GetRequiredText()))); +} + +void QmitkQuestionWidget::Reset() +{ + this->HideRequirement(); +} + +void QmitkQuestionWidget::SetRequirementVisible(bool visible) +{ + if (visible) + { + this->ShowRequirement(); + } + else + { + this->HideRequirement(); + } +} + +void QmitkQuestionWidget::ShowRequirement() +{ + this->setStyleSheet("QmitkQuestionWidget { border-color: red; }"); + m_RequiredLabel->show(); +} + +void QmitkQuestionWidget::HideRequirement() +{ + this->setStyleSheet(""); + m_RequiredLabel->hide(); +} + +void QmitkQuestionWidget::InsertLayout(QLayout* layout) +{ + m_Layout->insertLayout(1, layout); +} diff --git a/Modules/FormsUI/src/QmitkShortAnswerQuestionWidget.cpp b/Modules/FormsUI/src/QmitkShortAnswerQuestionWidget.cpp new file mode 100644 index 0000000000..09da4aa251 --- /dev/null +++ b/Modules/FormsUI/src/QmitkShortAnswerQuestionWidget.cpp @@ -0,0 +1,78 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include +#include + +#include +#include + +using namespace mitk::Forms; +using Self = QmitkShortAnswerQuestionWidget; + +QmitkShortAnswerQuestionWidget::QmitkShortAnswerQuestionWidget(QWidget* parent) + : QmitkQuestionWidget(parent), + m_Question(nullptr), + m_Layout(new QVBoxLayout), + m_LineEdit(new QLineEdit) +{ + m_LineEdit->setPlaceholderText("Your answer"); + m_Layout->addWidget(m_LineEdit); + + this->InsertLayout(m_Layout); + + connect(m_LineEdit, &QLineEdit::textEdited, this, &Self::OnTextEdited); +} + +QmitkShortAnswerQuestionWidget::~QmitkShortAnswerQuestionWidget() +{ +} + +QmitkQuestionWidget* QmitkShortAnswerQuestionWidget::CreateAnother(QWidget* parent) const +{ + return new QmitkShortAnswerQuestionWidget(parent); +} + +Question* QmitkShortAnswerQuestionWidget::GetQuestion() const +{ + return m_Question; +} + +void QmitkShortAnswerQuestionWidget::SetQuestion(Question* question) +{ + QmitkQuestionWidget::SetQuestion(question); + + auto shortAnswerQuestion = dynamic_cast(question); + + if (shortAnswerQuestion == nullptr) + mitkThrow() << "QmitkShortAnswerQuestionWidget only accepts ShortAnswerQuestion as question type!"; + + m_Question = shortAnswerQuestion; +} + +void QmitkShortAnswerQuestionWidget::Reset() +{ + m_LineEdit->clear(); + + if (m_Question) + m_Question->ClearResponses(); + + QmitkQuestionWidget::Reset(); +} + +void QmitkShortAnswerQuestionWidget::OnTextEdited(const QString& text) +{ + m_Question->SetResponse(text.toStdString()); + + if (m_Question->IsRequired()) + this->SetRequirementVisible(!m_Question->IsComplete()); +} diff --git a/Modules/FormsUI/src/mitkIQuestionWidgetFactory.cpp b/Modules/FormsUI/src/mitkIQuestionWidgetFactory.cpp new file mode 100644 index 0000000000..8895e58394 --- /dev/null +++ b/Modules/FormsUI/src/mitkIQuestionWidgetFactory.cpp @@ -0,0 +1,34 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include + +#include +#include +#include + +using namespace mitk::Forms::UI; + +IQuestionWidgetFactory* IQuestionWidgetFactory::GetInstance() +{ + static auto context = us::GetModuleContext(); + static us::ServiceReference serviceReference; + + if (!serviceReference) + serviceReference = context->GetServiceReference(); + + return serviceReference + ? context->GetService(serviceReference) + : nullptr; +} + +IQuestionWidgetFactory::~IQuestionWidgetFactory() = default; diff --git a/Modules/FormsUI/src/mitkModuleActivator.cpp b/Modules/FormsUI/src/mitkModuleActivator.cpp new file mode 100644 index 0000000000..2c4eb80675 --- /dev/null +++ b/Modules/FormsUI/src/mitkModuleActivator.cpp @@ -0,0 +1,50 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkModuleActivator.h" +#include "mitkQuestionWidgetFactory.h" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace mitk::Forms::UI; + +ModuleActivator::ModuleActivator() + : m_QuestionWidgetFactory(std::make_unique()) +{ +} + +ModuleActivator::~ModuleActivator() = default; + +void ModuleActivator::Load(us::ModuleContext* context) +{ + context->RegisterService(m_QuestionWidgetFactory.get()); + + this->RegisterQuestionWidget(); + this->RegisterQuestionWidget(); + this->RegisterQuestionWidget(); + this->RegisterQuestionWidget(); + this->RegisterQuestionWidget(); + this->RegisterQuestionWidget(); +} + +void ModuleActivator::Unload(us::ModuleContext*) +{ +} + +US_EXPORT_MODULE_ACTIVATOR(ModuleActivator) diff --git a/Modules/FormsUI/src/mitkModuleActivator.h b/Modules/FormsUI/src/mitkModuleActivator.h new file mode 100644 index 0000000000..3832600cd4 --- /dev/null +++ b/Modules/FormsUI/src/mitkModuleActivator.h @@ -0,0 +1,43 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkModuleActivator_h +#define mitkModuleActivator_h + +#include +#include +#include + +namespace mitk::Forms::UI +{ + class ModuleActivator : public us::ModuleActivator + { + public: + ModuleActivator(); + ~ModuleActivator() override; + + void Load(us::ModuleContext* context) override; + void Unload(us::ModuleContext* context) override; + + private: + template + void RegisterQuestionWidget() + { + auto question = std::make_unique(); + m_QuestionWidgetFactory->Register(question->GetType(), new TWidget); + } + + std::unique_ptr m_QuestionWidgetFactory; + }; +} + +#endif diff --git a/Modules/FormsUI/src/mitkQuestionWidgetFactory.cpp b/Modules/FormsUI/src/mitkQuestionWidgetFactory.cpp new file mode 100644 index 0000000000..5df045b9f4 --- /dev/null +++ b/Modules/FormsUI/src/mitkQuestionWidgetFactory.cpp @@ -0,0 +1,53 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkQuestionWidgetFactory.h" + +#include +#include + +#include + +using namespace mitk::Forms::UI; + +QuestionWidgetFactory::QuestionWidgetFactory() = default; + +QuestionWidgetFactory::~QuestionWidgetFactory() +{ +} + +void QuestionWidgetFactory::Register(const std::string& questionType, QmitkQuestionWidget* widgetPrototype) +{ + if (widgetPrototype == nullptr) + mitkThrow() << "Cannot register nullptr as widget prototype!"; + + if (m_WidgetPrototypes.find(questionType) != m_WidgetPrototypes.end()) + mitkThrow() << "A widget prototype is already registered for questions of type \"" << questionType << "\"!"; + + m_WidgetPrototypes[questionType].reset(widgetPrototype); +} + +QmitkQuestionWidget* QuestionWidgetFactory::Create(Question* question, QWidget* parent) const +{ + try + { + auto widget = m_WidgetPrototypes.at(question->GetType())->CreateAnother(parent); + widget->SetQuestion(question); + + return widget; + } + catch (const std::out_of_range&) + { + } + + return nullptr; +} diff --git a/Modules/FormsUI/src/mitkQuestionWidgetFactory.h b/Modules/FormsUI/src/mitkQuestionWidgetFactory.h new file mode 100644 index 0000000000..2104c7e51c --- /dev/null +++ b/Modules/FormsUI/src/mitkQuestionWidgetFactory.h @@ -0,0 +1,37 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkQuestionWidgetFactory_h +#define mitkQuestionWidgetFactory_h + +#include + +#include +#include + +namespace mitk::Forms::UI +{ + class QuestionWidgetFactory : public IQuestionWidgetFactory + { + public: + QuestionWidgetFactory(); + ~QuestionWidgetFactory() override; + + void Register(const std::string& questionType, QmitkQuestionWidget* widgetPrototype) override; + QmitkQuestionWidget* Create(Question* question, QWidget* parent = nullptr) const override; + + private: + std::unordered_map> m_WidgetPrototypes; + }; +} + +#endif diff --git a/Modules/ModuleList.cmake b/Modules/ModuleList.cmake index 427f9145f4..cd0dc4feb9 100644 --- a/Modules/ModuleList.cmake +++ b/Modules/ModuleList.cmake @@ -1,60 +1,61 @@ # The entries in the mitk_modules list must be # ordered according to their dependencies. set(MITK_MODULES Log Core Forms + FormsUI CommandLine CoreCmdApps AppUtil LegacyIO DataTypesExt Annotation LegacyGL AlgorithmsExt MapperExt DICOM DICOMQI DICOMTesting SceneSerializationBase PlanarFigure ImageDenoising ImageExtraction SceneSerialization Gizmo GraphAlgorithms Multilabel Chart ImageStatistics ContourModel SurfaceInterpolation BoundingShape Segmentation QtWidgets QtWidgetsExt ImageStatisticsUI SegmentationUI MatchPointRegistration MatchPointRegistrationUI Classification QtOverlays DICOMUI Remeshing Python QtPython Persistence RT RTUI IOExt XNAT RenderWindowManagerUI CEST BasicImageProcessing ModelFit ModelFitUI Pharmacokinetics PharmacokineticsUI DICOMPM ROI )