diff --git a/Examples/Plugins/PluginList.cmake b/Examples/Plugins/PluginList.cmake
index 4a4d4ec08e..4f595b8cec 100644
--- a/Examples/Plugins/PluginList.cmake
+++ b/Examples/Plugins/PluginList.cmake
@@ -1,17 +1,18 @@
# Plug-ins must be ordered according to their dependencies
set(MITK_EXAMPLE_PLUGINS
org.mitk.example.gui.minimalapplication:ON
org.mitk.example.gui.customviewer:ON
org.mitk.example.gui.customviewer.views:ON
org.mitk.example.gui.multipleperspectives:ON
org.mitk.example.gui.selectionserviceqt:ON
org.mitk.example.gui.selectionservicemitk:ON
org.mitk.example.gui.selectionservicemitk.views:ON
org.mitk.example.gui.extensionpointdefinition:ON
org.mitk.example.gui.extensionpointcontribution:ON
org.mitk.example.gui.regiongrowing:ON
org.mitk.example.gui.pcaexample:ON
org.mitk.example.gui.imaging:ON
+ org.mitk.example.gui.forms:ON
)
diff --git a/Examples/Plugins/org.mitk.example.gui.forms/CMakeLists.txt b/Examples/Plugins/org.mitk.example.gui.forms/CMakeLists.txt
new file mode 100644
index 0000000000..1f1cbb8764
--- /dev/null
+++ b/Examples/Plugins/org.mitk.example.gui.forms/CMakeLists.txt
@@ -0,0 +1,8 @@
+project(org_mitk_example_gui_forms)
+
+mitk_create_plugin(
+ EXPORT_DIRECTIVE FORMS_EXPORT
+ EXPORTED_INCLUDE_SUFFIXES src
+ MODULE_DEPENDS MitkFormsUI
+ NO_INSTALL
+)
diff --git a/Examples/Plugins/org.mitk.example.gui.forms/files.cmake b/Examples/Plugins/org.mitk.example.gui.forms/files.cmake
new file mode 100644
index 0000000000..69d97f7b42
--- /dev/null
+++ b/Examples/Plugins/org.mitk.example.gui.forms/files.cmake
@@ -0,0 +1,18 @@
+set(CPP_FILES
+ src/internal/mitkPluginActivator.cpp
+ src/internal/QmitkFormsExampleView.cpp
+)
+
+set(MOC_H_FILES
+ src/internal/mitkPluginActivator.h
+ src/internal/QmitkFormsExampleView.h
+)
+
+set(CACHED_RESOURCE_FILES
+ resources/FormsIcon.svg
+ plugin.xml
+)
+
+set(QRC_FILES
+ resources/FormsExample.qrc
+)
diff --git a/Examples/Plugins/org.mitk.example.gui.forms/manifest_headers.cmake b/Examples/Plugins/org.mitk.example.gui.forms/manifest_headers.cmake
new file mode 100644
index 0000000000..59fbd0ee21
--- /dev/null
+++ b/Examples/Plugins/org.mitk.example.gui.forms/manifest_headers.cmake
@@ -0,0 +1,5 @@
+set(Plugin-Name "MITK Forms Example")
+set(Plugin-Version "0.1")
+set(Plugin-Vendor "German Cancer Research Center (DKFZ)")
+set(Plugin-ContactAddress "https://www.mitk.org")
+set(Require-Plugin org.mitk.gui.qt.common)
diff --git a/Examples/Plugins/org.mitk.example.gui.forms/plugin.xml b/Examples/Plugins/org.mitk.example.gui.forms/plugin.xml
new file mode 100644
index 0000000000..f5fc563d96
--- /dev/null
+++ b/Examples/Plugins/org.mitk.example.gui.forms/plugin.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/Examples/Plugins/org.mitk.example.gui.forms/resources/ExampleForm.json b/Examples/Plugins/org.mitk.example.gui.forms/resources/ExampleForm.json
new file mode 100644
index 0000000000..4850ad4ad5
--- /dev/null
+++ b/Examples/Plugins/org.mitk.example.gui.forms/resources/ExampleForm.json
@@ -0,0 +1,67 @@
+{
+ "FileFormat": "MITK Form",
+ "Version": 1,
+ "Sections": [
+ {
+ "Title": "Example Form",
+ "Description": "This form was deserialized from a JSON file. Feel free to complete and submit it, resp. save its responses to a new or existing comma-separated value file.",
+ "Questions": [
+ {
+ "Text": "Is this is a multiple choice question?",
+ "Required": true,
+ "Type": "Multiple choice",
+ "Options": [
+ "Yes, it is.",
+ "No, it isn't. Wait... it actually is."
+ ],
+ "Other": true
+ },
+ {
+ "Text": "Is this a checkboxes question?",
+ "Type": "Checkboxes",
+ "Options": [
+ "Yes, it is.",
+ "It is indeed.",
+ "Yep, I'm sure."
+ ],
+ "Other": true
+ },
+ {
+ "Text": "Do you agree that this is a linear scale question?",
+ "Required": true,
+ "Type": "Linear scale",
+ "Range": [
+ 1,
+ 4
+ ],
+ "Labels": [
+ "Agree",
+ "Agree even more"
+ ]
+ },
+ {
+ "Text": "Is this a short-answer question?",
+ "Type": "Short answer"
+ }
+ ]
+ },
+ {
+ "Title": "Second section",
+ "Description": "Welcome to the second and last section of this form.",
+ "Questions": [
+ {
+ "Text": "Is this a dropdown question?",
+ "Type": "Drop-down",
+ "Options": [
+ "Yes, it is.",
+ "I'm not sure (yes)."
+ ]
+ },
+ {
+ "Text": "Is this a paragraph question?",
+ "Type": "Paragraph"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Examples/Plugins/org.mitk.example.gui.forms/resources/FormsExample.qrc b/Examples/Plugins/org.mitk.example.gui.forms/resources/FormsExample.qrc
new file mode 100644
index 0000000000..9b4072c7ea
--- /dev/null
+++ b/Examples/Plugins/org.mitk.example.gui.forms/resources/FormsExample.qrc
@@ -0,0 +1,6 @@
+
+
+ FormsIcon.svg
+ ExampleForm.json
+
+
diff --git a/Examples/Plugins/org.mitk.example.gui.forms/resources/FormsIcon.svg b/Examples/Plugins/org.mitk.example.gui.forms/resources/FormsIcon.svg
new file mode 100644
index 0000000000..2ba2d6773e
--- /dev/null
+++ b/Examples/Plugins/org.mitk.example.gui.forms/resources/FormsIcon.svg
@@ -0,0 +1,68 @@
+
+
+
+
diff --git a/Examples/Plugins/org.mitk.example.gui.forms/src/internal/QmitkFormsExampleView.cpp b/Examples/Plugins/org.mitk.example.gui.forms/src/internal/QmitkFormsExampleView.cpp
new file mode 100644
index 0000000000..4fc85f4f92
--- /dev/null
+++ b/Examples/Plugins/org.mitk.example.gui.forms/src/internal/QmitkFormsExampleView.cpp
@@ -0,0 +1,56 @@
+/*============================================================================
+
+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 "QmitkFormsExampleView.h"
+#include
+
+#include
+
+#include
+#include
+#include
+
+const std::string QmitkFormsExampleView::VIEW_ID = "org.mitk.views.example.forms";
+
+namespace
+{
+ QString ReadFileAsString(const QString& path)
+ {
+ QFile file(path);
+
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+ mitkThrow() << "Could not open file \"" << path << "\"!";
+
+ QTextStream stream(&file);
+ return stream.readAll();
+ }
+}
+
+QmitkFormsExampleView::QmitkFormsExampleView()
+ : m_Form(nlohmann::json::parse(ReadFileAsString(":/FormsExample/ExampleForm.json").toStdString()))
+{
+}
+
+QmitkFormsExampleView::~QmitkFormsExampleView()
+{
+}
+
+void QmitkFormsExampleView::CreateQtPartControl(QWidget* parent)
+{
+ auto layout = new QVBoxLayout(parent);
+ layout->addWidget(new QmitkForm(m_Form));
+ layout->addStretch();
+}
+
+void QmitkFormsExampleView::SetFocus()
+{
+}
diff --git a/Examples/Plugins/org.mitk.example.gui.forms/src/internal/QmitkFormsExampleView.h b/Examples/Plugins/org.mitk.example.gui.forms/src/internal/QmitkFormsExampleView.h
new file mode 100644
index 0000000000..f541a1fb75
--- /dev/null
+++ b/Examples/Plugins/org.mitk.example.gui.forms/src/internal/QmitkFormsExampleView.h
@@ -0,0 +1,36 @@
+/*============================================================================
+
+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 QmitkFormsExampleView_h
+#define QmitkFormsExampleView_h
+
+#include
+#include
+
+class QmitkFormsExampleView : public QmitkAbstractView
+{
+ Q_OBJECT
+
+public:
+ static const std::string VIEW_ID;
+
+ QmitkFormsExampleView();
+ ~QmitkFormsExampleView() override;
+
+ void CreateQtPartControl(QWidget* parent) override;
+ void SetFocus() override;
+
+private:
+ mitk::Forms::Form m_Form;
+};
+
+#endif
diff --git a/Examples/Plugins/org.mitk.example.gui.forms/src/internal/mitkPluginActivator.cpp b/Examples/Plugins/org.mitk.example.gui.forms/src/internal/mitkPluginActivator.cpp
new file mode 100644
index 0000000000..428d756dbd
--- /dev/null
+++ b/Examples/Plugins/org.mitk.example.gui.forms/src/internal/mitkPluginActivator.cpp
@@ -0,0 +1,23 @@
+/*============================================================================
+
+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 "mitkPluginActivator.h"
+#include "QmitkFormsExampleView.h"
+
+void mitk::mitkPluginActivator::start(ctkPluginContext* context)
+{
+ BERRY_REGISTER_EXTENSION_CLASS(QmitkFormsExampleView, context)
+}
+
+void mitk::mitkPluginActivator::stop(ctkPluginContext*)
+{
+}
diff --git a/Examples/Plugins/org.mitk.example.gui.forms/src/internal/mitkPluginActivator.h b/Examples/Plugins/org.mitk.example.gui.forms/src/internal/mitkPluginActivator.h
new file mode 100644
index 0000000000..72d1f42520
--- /dev/null
+++ b/Examples/Plugins/org.mitk.example.gui.forms/src/internal/mitkPluginActivator.h
@@ -0,0 +1,32 @@
+/*============================================================================
+
+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 mitkPluginActivator_h
+#define mitkPluginActivator_h
+
+#include
+
+namespace mitk
+{
+ class mitkPluginActivator : public QObject, public ctkPluginActivator
+ {
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org_mitk_gui_example_forms")
+ Q_INTERFACES(ctkPluginActivator)
+
+ public:
+ void start(ctkPluginContext* context) override;
+ void stop(ctkPluginContext* context) override;
+ };
+}
+
+#endif
diff --git a/Modules/Forms/CMakeLists.txt b/Modules/Forms/CMakeLists.txt
new file mode 100644
index 0000000000..83366a3827
--- /dev/null
+++ b/Modules/Forms/CMakeLists.txt
@@ -0,0 +1,7 @@
+mitk_create_module(
+ INCLUDE_DIRS
+ PUBLIC include
+ PRIVATE src
+ DEPENDS
+ PUBLIC MitkCore
+)
diff --git a/Modules/Forms/files.cmake b/Modules/Forms/files.cmake
new file mode 100644
index 0000000000..ffa7c50a1e
--- /dev/null
+++ b/Modules/Forms/files.cmake
@@ -0,0 +1,31 @@
+set(H_FILES
+ include/mitkCheckboxesQuestion.h
+ include/mitkDropdownQuestion.h
+ include/mitkForm.h
+ include/mitkIQuestionFactory.h
+ include/mitkLinearScaleQuestion.h
+ include/mitkMultipleChoiceQuestion.h
+ include/mitkParagraphQuestion.h
+ include/mitkQuestion.h
+ include/mitkQuestionWithOptions.h
+ include/mitkQuestionWithOtherOption.h
+ include/mitkShortAnswerQuestion.h
+ include/mitkTextQuestion.h
+)
+
+set(CPP_FILES
+ mitkCheckboxesQuestion.cpp
+ mitkDropdownQuestion.cpp
+ mitkForm.cpp
+ mitkIQuestionFactory.cpp
+ mitkLinearScaleQuestion.cpp
+ mitkModuleActivator.cpp
+ mitkMultipleChoiceQuestion.cpp
+ mitkParagraphQuestion.cpp
+ mitkQuestion.cpp
+ mitkQuestionFactory.cpp
+ mitkQuestionWithOptions.cpp
+ mitkQuestionWithOtherOption.cpp
+ mitkShortAnswerQuestion.cpp
+ mitkTextQuestion.cpp
+)
diff --git a/Modules/Forms/include/mitkCheckboxesQuestion.h b/Modules/Forms/include/mitkCheckboxesQuestion.h
new file mode 100644
index 0000000000..2dac18b3a1
--- /dev/null
+++ b/Modules/Forms/include/mitkCheckboxesQuestion.h
@@ -0,0 +1,49 @@
+/*============================================================================
+
+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 mitkCheckboxesQuestion_h
+#define mitkCheckboxesQuestion_h
+
+#include
+
+namespace mitk::Forms
+{
+ /** \brief A Question whose possible responses are represented by checkboxes.
+ *
+ * The question can have multiple responses at once, including an optional
+ * free text response.
+ *
+ * \sa MultipleChoiceQuestion, DropdownQuestion
+ */
+ class MITKFORMS_EXPORT CheckboxesQuestion : public QuestionWithOtherOption
+ {
+ public:
+ ~CheckboxesQuestion() override;
+
+ std::string GetType() const override;
+ Question* CreateAnother() const override;
+
+ void FromJSON(const nlohmann::ordered_json& j) override;
+ void ToJSON(nlohmann::ordered_json& j) const override;
+
+ using QuestionWithOptions::AddResponse;
+ using QuestionWithOptions::RemoveResponse;
+
+ using QuestionWithOtherOption::AddOtherResponse;
+ using QuestionWithOtherOption::RemoveOtherResponse;
+ };
+
+ MITKFORMS_EXPORT void from_json(const nlohmann::ordered_json& j, CheckboxesQuestion& q);
+ MITKFORMS_EXPORT void to_json(nlohmann::ordered_json& j, const CheckboxesQuestion& q);
+}
+
+#endif
diff --git a/Modules/Forms/include/mitkDropdownQuestion.h b/Modules/Forms/include/mitkDropdownQuestion.h
new file mode 100644
index 0000000000..ac93d62b3b
--- /dev/null
+++ b/Modules/Forms/include/mitkDropdownQuestion.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 mitkDropdownQuestion_h
+#define mitkDropdownQuestion_h
+
+#include
+
+namespace mitk::Forms
+{
+ /** \brief A Question whose possible responses are represented by a combo box.
+ *
+ * The question can have a single response.
+ *
+ * \sa MultipleChoiceQuestion, CheckboxesQuestion
+ */
+ class MITKFORMS_EXPORT DropdownQuestion : public QuestionWithOptions
+ {
+ public:
+ ~DropdownQuestion() override;
+
+ std::string GetType() const override;
+ Question* CreateAnother() const override;
+
+ void FromJSON(const nlohmann::ordered_json& j) override;
+ void ToJSON(nlohmann::ordered_json& j) const override;
+
+ using QuestionWithOptions::SetResponse;
+ };
+
+ MITKFORMS_EXPORT void from_json(const nlohmann::ordered_json& j, DropdownQuestion& q);
+ MITKFORMS_EXPORT void to_json(nlohmann::ordered_json& j, const DropdownQuestion& q);
+}
+
+#endif
diff --git a/Modules/Forms/include/mitkForm.h b/Modules/Forms/include/mitkForm.h
new file mode 100644
index 0000000000..fde5796820
--- /dev/null
+++ b/Modules/Forms/include/mitkForm.h
@@ -0,0 +1,99 @@
+/*============================================================================
+
+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 mitkForm_h
+#define mitkForm_h
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+namespace mitk::Forms
+{
+ class Question;
+
+ /** A form consisting of questions possibly divided into multiple sections/pages.
+ *
+ * A form always has at least a single section, which is also used to define the form's general
+ * title and description. Helper methods like SetTitle() or SetDescription() can be used to
+ * conveniently set these properties of the first section.
+ */
+ class MITKFORMS_EXPORT Form
+ {
+ public:
+ class MITKFORMS_EXPORT Section
+ {
+ public:
+ explicit Section(const std::string& title = "", const std::string& description = "");
+ ~Section();
+
+ Section(Section&& other) noexcept;
+ Section& operator=(Section&& other) noexcept;
+
+ std::string GetTitle() const;
+ void SetTitle(const std::string& title);
+
+ std::string GetDescription() const;
+ void SetDescription(const std::string& description);
+
+ std::vector GetQuestions() const;
+ void AddQuestion(Question* question);
+
+ private:
+ std::string m_Title;
+ std::string m_Description;
+ std::vector> m_Questions;
+ };
+
+ explicit Form(const std::string& title = "", const std::string& description = "");
+ ~Form();
+
+ Form(Form&& other) noexcept;
+ Form& operator=(Form&& other) noexcept;
+
+ Section& AddSection(const std::string& title = "", const std::string& description = "");
+ int GetNumberOfSections() const;
+
+ Section& GetSection(int index);
+ const Section& GetSection(int index) const;
+
+ std::string GetTitle() const;
+ void SetTitle(const std::string& title);
+
+ std::string GetDescription() const;
+ void SetDescription(const std::string& description);
+
+ std::vector GetQuestions() const;
+ void AddQuestion(Question* question);
+
+ std::vector::const_iterator begin() const;
+ std::vector::const_iterator end() const;
+
+ std::vector::iterator begin();
+ std::vector::iterator end();
+
+ private:
+ std::vector m_Sections;
+ };
+
+ MITKFORMS_EXPORT void SubmitToCSV(const Form& form, const fs::path& csvPath);
+
+ MITKFORMS_EXPORT void from_json(const nlohmann::ordered_json& j, Form& f);
+ MITKFORMS_EXPORT void to_json(nlohmann::ordered_json& j, const Form& f);
+}
+
+#endif
diff --git a/Modules/Forms/include/mitkIQuestionFactory.h b/Modules/Forms/include/mitkIQuestionFactory.h
new file mode 100644
index 0000000000..6dcd8fffa6
--- /dev/null
+++ b/Modules/Forms/include/mitkIQuestionFactory.h
@@ -0,0 +1,61 @@
+/*============================================================================
+
+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 mitkIQuestionFactory_h
+#define mitkIQuestionFactory_h
+
+#include
+#include
+
+#include
+
+namespace mitk::Forms
+{
+ class Question;
+
+ /** \brief Register subclasses of Question to create instances of them based on their type string.
+ *
+ * This is a service interface. Obtain a pointer to its single instance via GetInstance().
+ *
+ * Each Question subclass must be registered by calling Register(), which is typically done
+ * in the module activator class. After Question subclasses are registered, instances of them
+ * can be created with Create() based on their type string. This mechanism is primarily used
+ * for the deserialization of questions.
+ */
+ class MITKFORMS_EXPORT IQuestionFactory
+ {
+ public:
+ /** \brief Obtain a pointer to the single instance of this service.
+ */
+ static IQuestionFactory* GetInstance();
+
+ virtual ~IQuestionFactory();
+
+ /** \brief Register a Question subclass for the instance creation based on its type string.
+ *
+ * The service takes over ownership of the passed Question pointer.
+ *
+ * \sa Question::GetType()
+ */
+ virtual void Register(Question* question) = 0;
+
+ /** \brief Create an instance of a Question subclass based on its type string.
+ *
+ * \sa Question::GetType(), Question::CreateAnother()
+ */
+ virtual Question* Create(const std::string& type) const = 0;
+ };
+}
+
+MITK_DECLARE_SERVICE_INTERFACE(mitk::Forms::IQuestionFactory, "org.mitk.Forms.IQuestionFactory")
+
+#endif
diff --git a/Modules/Forms/include/mitkLinearScaleQuestion.h b/Modules/Forms/include/mitkLinearScaleQuestion.h
new file mode 100644
index 0000000000..c5b01cdce3
--- /dev/null
+++ b/Modules/Forms/include/mitkLinearScaleQuestion.h
@@ -0,0 +1,72 @@
+/*============================================================================
+
+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 mitkLinearScaleQuestion_h
+#define mitkLinearScaleQuestion_h
+
+#include
+#include
+#include
+
+namespace mitk::Forms
+{
+ /** \brief A Question whose possible responses are represented by a discrete linear scale.
+ *
+ * The question can have a single response, which is one of the numbers within the allowed
+ * range. The range must start with 0 or 1 and end with any number between 2 and 10 (1 to 5
+ * by default).
+ */
+ class MITKFORMS_EXPORT LinearScaleQuestion : public Question
+ {
+ public:
+ LinearScaleQuestion();
+ ~LinearScaleQuestion() override;
+
+ std::string GetType() const override;
+ Question* CreateAnother() const override;
+
+ void FromJSON(const nlohmann::ordered_json& j) override;
+ void ToJSON(nlohmann::ordered_json& j) const override;
+
+ std::vector GetResponsesAsStrings() const override;
+ void ClearResponses() override;
+
+ bool IsComplete() const override;
+
+ std::optional GetResponse() const;
+ void SetResponse(int response);
+
+ std::pair GetRange() const;
+
+ /** \brief Set the allowed range of numbers for a valid response.
+ *
+ * A valid range must start with 0 or 1 and end with any number between 2 and 10 (1 to 5
+ * by default).
+ *
+ * \exception Exception Invalid range.
+ */
+ void SetRange(const std::pair& range);
+
+ std::pair GetRangeLabels() const;
+ void SetRangeLabels(const std::pair& labels);
+
+ private:
+ std::optional m_Response;
+ std::pair m_Range;
+ std::pair m_Labels;
+ };
+
+ MITKFORMS_EXPORT void from_json(const nlohmann::ordered_json& j, LinearScaleQuestion& q);
+ MITKFORMS_EXPORT void to_json(nlohmann::ordered_json& j, const LinearScaleQuestion& q);
+}
+
+#endif
diff --git a/Modules/Forms/include/mitkMultipleChoiceQuestion.h b/Modules/Forms/include/mitkMultipleChoiceQuestion.h
new file mode 100644
index 0000000000..9ff48b749f
--- /dev/null
+++ b/Modules/Forms/include/mitkMultipleChoiceQuestion.h
@@ -0,0 +1,45 @@
+/*============================================================================
+
+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 mitkMultipleChoiceQuestion_h
+#define mitkMultipleChoiceQuestion_h
+
+#include
+
+namespace mitk::Forms
+{
+ /** \brief A Question whose possible responses are represented by radio buttons.
+ *
+ * The question can have a single response, which can optionally be free text.
+ *
+ * \sa CheckboxesQuestion, DropdownQuestion
+ */
+ class MITKFORMS_EXPORT MultipleChoiceQuestion : public QuestionWithOtherOption
+ {
+ public:
+ ~MultipleChoiceQuestion() override;
+
+ std::string GetType() const override;
+ Question* CreateAnother() const override;
+
+ void FromJSON(const nlohmann::ordered_json& j) override;
+ void ToJSON(nlohmann::ordered_json& j) const override;
+
+ using QuestionWithOptions::SetResponse;
+ using QuestionWithOtherOption::SetOtherResponse;
+ };
+
+ MITKFORMS_EXPORT void from_json(const nlohmann::ordered_json& j, MultipleChoiceQuestion& q);
+ MITKFORMS_EXPORT void to_json(nlohmann::ordered_json& j, const MultipleChoiceQuestion& q);
+}
+
+#endif
diff --git a/Modules/Forms/include/mitkParagraphQuestion.h b/Modules/Forms/include/mitkParagraphQuestion.h
new file mode 100644
index 0000000000..68574c8a0d
--- /dev/null
+++ b/Modules/Forms/include/mitkParagraphQuestion.h
@@ -0,0 +1,40 @@
+/*============================================================================
+
+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 mitkParagraphQuestion_h
+#define mitkParagraphQuestion_h
+
+#include
+
+namespace mitk::Forms
+{
+ /** \brief A Question whose possible response is free text.
+ *
+ * \sa ShortAnswerQuestion
+ */
+ class MITKFORMS_EXPORT ParagraphQuestion : public TextQuestion
+ {
+ public:
+ ~ParagraphQuestion() override;
+
+ std::string GetType() const override;
+ Question* CreateAnother() const override;
+
+ void FromJSON(const nlohmann::ordered_json& j) override;
+ void ToJSON(nlohmann::ordered_json& j) const override;
+ };
+
+ MITKFORMS_EXPORT void from_json(const nlohmann::ordered_json& j, ParagraphQuestion& q);
+ MITKFORMS_EXPORT void to_json(nlohmann::ordered_json& j, const ParagraphQuestion& q);
+}
+
+#endif
diff --git a/Modules/Forms/include/mitkQuestion.h b/Modules/Forms/include/mitkQuestion.h
new file mode 100644
index 0000000000..56829ecb5a
--- /dev/null
+++ b/Modules/Forms/include/mitkQuestion.h
@@ -0,0 +1,238 @@
+/*============================================================================
+
+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 mitkQuestion_h
+#define mitkQuestion_h
+
+#include
+
+#include
+#include
+
+#include
+
+namespace mitk::Forms
+{
+ /** \brief Abstract base class for all types of questions used in a Form.
+ *
+ * Please make sure to read the full documentation of the pure virtual functions in particular to
+ * fully understand implications and requirements.
+ */
+ class MITKFORMS_EXPORT Question
+ {
+ public:
+ Question();
+ virtual ~Question();
+
+ /** \brief Get the literal question.
+ */
+ std::string GetQuestionText() const;
+
+ /** \brief Set the literal question.
+ */
+ void SetQuestionText(const std::string& question);
+
+ /** \brief Check whether a response to this question is required to complete a form.
+ */
+ bool IsRequired() const;
+
+ /** \brief Set whether a resonse to this question is required to complete a form.
+ *
+ * A question is not required by default.
+ */
+ void SetRequired(bool required = true);
+
+ /** \brief Get the text that should be displayed to clearly mark a question as required.
+ *
+ * This text is typically displayed as soon as a response to a required question is missing.
+ * The method can be overridden to customize the default text, e.g. for more complex types
+ * of questions, where specific guidance is beneficial.
+ *
+ * \sa IsComplete()
+ */
+ virtual std::string GetRequiredText() const;
+
+ /** \name Pure virtual functions
+ *
+ * Question is an abstract base class. Derive from this class to add a new type of question
+ * and override the following pure virtual functions. Please read the full documentation for
+ * all of these functions to fully understand implications and requirements.
+ *
+ * Do not forget to register any new type of question by calling IQuestionFactory::Register()
+ * like it is done in this module's activator class.
+ *
+ * \sa IQuestionFactory
+ * \{
+ */
+
+ /** \brief Return the type of a question as string, e.g. "Multiple choice" or "Drop-down".
+ *
+ * This method is essential for the deserialization of questions into their correct type, resp.
+ * derived class. The type string is used by IQuestionFactory to look up a registered prototype
+ * instance of a certain type to create another instance of it.
+ *
+ * The type string does not have to match the class name or follow any other convention except
+ * for it must be unique amongst all question types. Prefer natural language like in the
+ * examples above in case it is used in a user interface to display a question's type.
+ *
+ * \code{.cpp}
+ * std::string RhetoricalQuestion::GetType() const
+ * {
+ * return "Rhetorical";
+ * }
+ * \endcode
+ *
+ * \sa CreateAnother()
+ */
+ virtual std::string GetType() const = 0;
+
+ /** \brief Create a new instance of the derived question class type.
+ *
+ * This method is mainly used by IQuestionFactory to create new instances from registered
+ * prototype instances based on a type string.
+ *
+ * \code{.cpp}
+ * Question* RhetoricalQuestion::CreateAnother() const
+ * {
+ * return new RhetoricalQuestion;
+ * }
+ * \endcode
+ *
+ * \sa GetType()
+ */
+ virtual Question* CreateAnother() const = 0;
+
+ /** \brief Return the question's response(s) as strings.
+ *
+ * This is the single common generic interface for retrieving responses from all types
+ * of questions. It is typically used to store responses in text-based formats like CSV.
+ *
+ * It is implemented as vector since certain types of questions like a checkboxes question
+ * may support multiple responses. Otherwise, return a vector with a single element.
+ *
+ * \code{.cpp}
+ * std::vector RhetoricalQuestion::GetResponsesAsStrings() const
+ * {
+ * return { m_Response };
+ * }
+ * \endcode
+ */
+ virtual std::vector GetResponsesAsStrings() const = 0;
+
+ /** \brief Clear the/all response(s).
+ */
+ virtual void ClearResponses() = 0;
+
+ /** \brief Check if a question is considered to be answered completely.
+ *
+ * This method is typically called when IsRequired() returns \c true to determine
+ * whether the requirements are fulfilled.
+ */
+ virtual bool IsComplete() const = 0;
+
+ /** \brief Deserialize from JSON.
+ *
+ * Polymorphism and the use of base class pointers make it necessary to implement serialization
+ * through member functions. Using the pure native approach of the "JSON for Modern C++" library
+ * via free functions would lead to partially serialized instances of derived classes.
+ *
+ * The actual implementation can and should still be located in a corresponding %from_json()
+ * free function but we also need the indirection through this member function.
+ *
+ * \code{.cpp}
+ * void RhetoricalQuestion::FromJSON(const nlohmann::ordered_json& j)
+ * {
+ * from_json(j, *this);
+ * }
+ * \endcode
+ *
+ * \sa from_json(const nlohmann::ordered_json& j, Question& q)
+ */
+ virtual void FromJSON(const nlohmann::ordered_json& j) = 0;
+
+ /** \brief Serialize to JSON.
+ *
+ * Polymorphism and the use of base class pointers make it necessary to implement serialization
+ * through member functions. Using the pure native approach of the "JSON for Modern C++" library
+ * via free functions would lead to partially serialized instances of derived classes.
+ *
+ * The actual implementation can and should still be located in a corresponding %to_json() free
+ * function but we also need the indirection through this member function.
+ *
+ * \code{.cpp}
+ * void RhetoricalQuestion::ToJSON(nlohmann::ordered_json& j) const
+ * {
+ * to_json(j, *this);
+ * }
+ * \endcode
+ *
+ * \sa to_json(nlohmann::ordered_json& j, const Question& q)
+ */
+ virtual void ToJSON(nlohmann::ordered_json& j) const = 0;
+
+ /**\}*/
+
+ private:
+ std::string m_QuestionText;
+ bool m_IsRequired;
+ };
+
+ /** \brief Deserialize from JSON.
+ *
+ * Polymorphism and the use of base class pointers make it necessary to implement serialization
+ * through member functions. Using the pure native approach of the "JSON for Modern C++" library
+ * via free functions would lead to partially serialized instances of derived classes.
+ *
+ * The actual implementation can and should still be located in a %from_json() free function but we
+ * also need the indirection through a corresponding member function.
+ *
+ * Overloaded functions for derived Question classes should reuse their base class versions of
+ * the function.
+ *
+ * \code{.cpp}
+ * void mitk::Forms::from_json(const ordered_json& j, RhetoricalQuestion& q)
+ * {
+ * from_json(j, static_cast(q));
+ * // TODO: Deserialization of members exclusive to the derived class...
+ * }
+ * \endcode
+ *
+ * \sa Question::FromJSON()
+ */
+ MITKFORMS_EXPORT void from_json(const nlohmann::ordered_json& j, Question& q);
+
+ /** \brief Serialize to JSON.
+ *
+ * Polymorphism and the use of base class pointers make it necessary to implement serialization
+ * through member functions. Using the pure native approach of the "JSON for Modern C++" library
+ * via free functions would lead to partially serialized instances of derived classes.
+ *
+ * The actual implementation can and should still be located in a %to_json() free function but we
+ * also need the indirection through a corresponding member function.
+ *
+ * Overloaded functions for derived Question classes should reuse their base class versions of
+ * the function.
+ *
+ * \code{.cpp}
+ * void mitk::Forms::to_json(nlohmann::ordered_json& j, const RhetoricalQuestion& q)
+ * {
+ * to_json(j, static_cast(q));
+ * // TODO: Serialization of members exclusive to the derived class...
+ * }
+ * \endcode
+ *
+ * \sa Question::ToJSON()
+ */
+ MITKFORMS_EXPORT void to_json(nlohmann::ordered_json& j, const Question& q);
+}
+
+#endif
diff --git a/Modules/Forms/include/mitkQuestionWithOptions.h b/Modules/Forms/include/mitkQuestionWithOptions.h
new file mode 100644
index 0000000000..4003ded473
--- /dev/null
+++ b/Modules/Forms/include/mitkQuestionWithOptions.h
@@ -0,0 +1,79 @@
+/*============================================================================
+
+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 mitkQuestionWithOptions_h
+#define mitkQuestionWithOptions_h
+
+#include
+#include
+#include
+
+namespace mitk::Forms
+{
+ /** \brief Base class for questions with options to choose from as response.
+ *
+ * This base class represents both, questions with mutually exclusive options as well as
+ * questions with multiple answer options. Derived classes should use using-declarations
+ * to change the visibility of the respective protected member functions to public, i.e.,
+ * SetResponse() vs. AddResponse() and RemoveResponse().
+ */
+ class MITKFORMS_EXPORT QuestionWithOptions : public Question
+ {
+ public:
+ ~QuestionWithOptions() override;
+
+ std::vector GetResponsesAsStrings() const override;
+ void ClearResponses() override;
+
+ bool IsComplete() const override;
+
+ /** \brief Add a non-exclusive option as possible answer to the question.
+ *
+ * The option is appended to the list of already existing options.
+ *
+ * \return The index of the option
+ */
+ size_t AddOption(const std::string& option);
+
+ std::vector GetOptions() const;
+
+ protected:
+ /** \brief Add one of the possible answer options to the responses.
+ *
+ * Indexes to responses are inserted in ascending order.
+ *
+ * \sa AddOption()
+ */
+ void AddResponse(size_t i);
+
+ /** \brief Remove one of the already given responses.
+ *
+ * \sa AddOption(), AddResponse()
+ */
+ void RemoveResponse(size_t i);
+
+ /** \brief Set one of the possible answer options as the single response.
+ *
+ * \note This will remove any responses added by AddResponse().
+ */
+ virtual void SetResponse(size_t i);
+
+ private:
+ std::vector m_Options;
+ std::set m_Responses;
+ };
+
+ MITKFORMS_EXPORT void from_json(const nlohmann::ordered_json& j, QuestionWithOptions& q);
+ MITKFORMS_EXPORT void to_json(nlohmann::ordered_json& j, const QuestionWithOptions& q);
+}
+
+#endif
diff --git a/Modules/Forms/include/mitkQuestionWithOtherOption.h b/Modules/Forms/include/mitkQuestionWithOtherOption.h
new file mode 100644
index 0000000000..5850dcfb9d
--- /dev/null
+++ b/Modules/Forms/include/mitkQuestionWithOtherOption.h
@@ -0,0 +1,79 @@
+/*============================================================================
+
+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 mitkQuestionWithOtherOption_h
+#define mitkQuestionWithOtherOption_h
+
+#include
+#include
+
+namespace mitk::Forms
+{
+ /** \brief Base class for questions with options that may also have a free text option.
+ *
+ * This base class is an extension of QuestionWithOptions representing questions that
+ * may also have a free text option, typically labeled "Other" in forms/surveys.
+ *
+ * Just like with QuestionWithOptions, depending on the exclusivity of the answer
+ * options, use using-declarations in derived classes to change the visibility of
+ * the respective protected member functions to public, i.e., SetOtherResponse()
+ * vs. AddOtherResponse() and RemoveOtherResponse().
+ */
+ class MITKFORMS_EXPORT QuestionWithOtherOption : public QuestionWithOptions
+ {
+ public:
+ QuestionWithOtherOption();
+ ~QuestionWithOtherOption() override;
+
+ std::vector GetResponsesAsStrings() const override;
+ void ClearResponses() override;
+
+ bool IsComplete() const override;
+
+ /** \brief Query whether this question actually has an "Other" option.
+ */
+ bool HasOtherOption() const;
+
+ /** \brief Switch on the "Other" option.
+ *
+ * By default, a question does not have an "Other" option.
+ */
+ void EnableOtherOption();
+
+ protected:
+ void SetResponse(size_t i) override;
+
+ /** \brief Add the free text given as "Other" option to the list of responses.
+ *
+ * \note A question can only have a single "Other" response. Consequtive calls
+ * to this method will override the previously set "Other" response.
+ */
+ void AddOtherResponse(const std::string& response);
+
+ /** \brief Remove the "Other" response from the list of already given responses.
+ */
+ void RemoveOtherResponse();
+
+ /** \brief Set the "Other" response as single exclusive response to this question.
+ */
+ void SetOtherResponse(const std::string& response);
+
+ private:
+ bool m_HasOtherOption;
+ std::optional m_OtherResponse;
+ };
+
+ MITKFORMS_EXPORT void from_json(const nlohmann::ordered_json& j, QuestionWithOtherOption& q);
+ MITKFORMS_EXPORT void to_json(nlohmann::ordered_json& j, const QuestionWithOtherOption& q);
+}
+
+#endif
diff --git a/Modules/Forms/include/mitkShortAnswerQuestion.h b/Modules/Forms/include/mitkShortAnswerQuestion.h
new file mode 100644
index 0000000000..4d90af707e
--- /dev/null
+++ b/Modules/Forms/include/mitkShortAnswerQuestion.h
@@ -0,0 +1,40 @@
+/*============================================================================
+
+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 mitkShortAnswerQuestion_h
+#define mitkShortAnswerQuestion_h
+
+#include
+
+namespace mitk::Forms
+{
+ /** \brief A Question whose possible response is a short single-line free text.
+ *
+ * \sa ParagraphQuestion
+ */
+ class MITKFORMS_EXPORT ShortAnswerQuestion : public TextQuestion
+ {
+ public:
+ ~ShortAnswerQuestion() override;
+
+ std::string GetType() const override;
+ Question* CreateAnother() const override;
+
+ void FromJSON(const nlohmann::ordered_json& j) override;
+ void ToJSON(nlohmann::ordered_json& j) const override;
+ };
+
+ MITKFORMS_EXPORT void from_json(const nlohmann::ordered_json& j, ShortAnswerQuestion& q);
+ MITKFORMS_EXPORT void to_json(nlohmann::ordered_json& j, const ShortAnswerQuestion& q);
+}
+
+#endif
diff --git a/Modules/Forms/include/mitkTextQuestion.h b/Modules/Forms/include/mitkTextQuestion.h
new file mode 100644
index 0000000000..ac88c1254d
--- /dev/null
+++ b/Modules/Forms/include/mitkTextQuestion.h
@@ -0,0 +1,38 @@
+/*============================================================================
+
+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 mitkTextQuestion_h
+#define mitkTextQuestion_h
+
+#include
+
+namespace mitk::Forms
+{
+ class MITKFORMS_EXPORT TextQuestion : public Question
+ {
+ public:
+ ~TextQuestion() override;
+
+ std::vector GetResponsesAsStrings() const override;
+ void ClearResponses() override;
+
+ bool IsComplete() const override;
+
+ std::string GetResponse() const;
+ void SetResponse(const std::string& response);
+
+ private:
+ std::string m_Response;
+ };
+}
+
+#endif
diff --git a/Modules/Forms/src/mitkCheckboxesQuestion.cpp b/Modules/Forms/src/mitkCheckboxesQuestion.cpp
new file mode 100644
index 0000000000..4945d756c9
--- /dev/null
+++ b/Modules/Forms/src/mitkCheckboxesQuestion.cpp
@@ -0,0 +1,48 @@
+/*============================================================================
+
+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
+
+using namespace mitk::Forms;
+using namespace nlohmann;
+
+CheckboxesQuestion::~CheckboxesQuestion() = default;
+
+std::string CheckboxesQuestion::GetType() const
+{
+ return "Checkboxes";
+}
+
+Question* CheckboxesQuestion::CreateAnother() const
+{
+ return new CheckboxesQuestion;
+}
+
+void CheckboxesQuestion::FromJSON(const nlohmann::ordered_json& j)
+{
+ from_json(j, *this);
+}
+
+void CheckboxesQuestion::ToJSON(nlohmann::ordered_json& j) const
+{
+ to_json(j, *this);
+}
+
+void mitk::Forms::from_json(const ordered_json& j, CheckboxesQuestion& q)
+{
+ from_json(j, static_cast(q));
+}
+
+void mitk::Forms::to_json(ordered_json& j, const CheckboxesQuestion& q)
+{
+ to_json(j, static_cast(q));
+}
diff --git a/Modules/Forms/src/mitkDropdownQuestion.cpp b/Modules/Forms/src/mitkDropdownQuestion.cpp
new file mode 100644
index 0000000000..4733262450
--- /dev/null
+++ b/Modules/Forms/src/mitkDropdownQuestion.cpp
@@ -0,0 +1,48 @@
+/*============================================================================
+
+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
+
+using namespace mitk::Forms;
+using namespace nlohmann;
+
+DropdownQuestion::~DropdownQuestion() = default;
+
+std::string DropdownQuestion::GetType() const
+{
+ return "Drop-down";
+}
+
+Question* DropdownQuestion::CreateAnother() const
+{
+ return new DropdownQuestion;
+}
+
+void DropdownQuestion::FromJSON(const nlohmann::ordered_json& j)
+{
+ from_json(j, *this);
+}
+
+void DropdownQuestion::ToJSON(nlohmann::ordered_json& j) const
+{
+ to_json(j, *this);
+}
+
+void mitk::Forms::from_json(const ordered_json& j, DropdownQuestion& q)
+{
+ from_json(j, static_cast(q));
+}
+
+void mitk::Forms::to_json(ordered_json& j, const DropdownQuestion& q)
+{
+ to_json(j, static_cast(q));
+}
diff --git a/Modules/Forms/src/mitkForm.cpp b/Modules/Forms/src/mitkForm.cpp
new file mode 100644
index 0000000000..7b846c90c3
--- /dev/null
+++ b/Modules/Forms/src/mitkForm.cpp
@@ -0,0 +1,306 @@
+/*============================================================================
+
+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 namespace nlohmann;
+
+namespace
+{
+ std::string GetCurrentISO8601DateTime()
+ {
+ std::timespec ts;
+ std::timespec_get(&ts, TIME_UTC);
+ std::array buffer{}; // YYYY-MM-DDThh:mm:ssZ
+
+ std::strftime(buffer.data(), buffer.size(), "%FT%TZ", std::gmtime(&ts.tv_sec));
+
+ return buffer.data();
+ }
+}
+
+Form::Section::Section(const std::string& title, const std::string& description)
+ : m_Title(title),
+ m_Description(description)
+{
+}
+
+Form::Section::~Section() = default;
+
+Form::Section::Section(Section&& other) noexcept = default;
+
+Form::Section& Form::Section::operator=(Section&& other) noexcept = default;
+
+std::string Form::Section::GetTitle() const
+{
+ return m_Title;
+}
+
+void Form::Section::SetTitle(const std::string& title)
+{
+ m_Title = title;
+}
+
+std::string Form::Section::GetDescription() const
+{
+ return m_Description;
+}
+
+void Form::Section::SetDescription(const std::string& description)
+{
+ m_Description = description;
+}
+
+std::vector Form::Section::GetQuestions() const
+{
+ std::vector questions;
+ questions.reserve(m_Questions.size());
+
+ for (const auto& question : m_Questions)
+ questions.push_back(question.get());
+
+ return questions;
+}
+
+void Form::Section::AddQuestion(Question* question)
+{
+ m_Questions.emplace_back(question);
+}
+
+Form::Form(const std::string& title, const std::string& description)
+{
+ m_Sections.emplace_back(title, description);
+}
+
+Form::~Form() = default;
+
+Form::Form(Form&& other) noexcept = default;
+
+Form& Form::operator=(Form&& other) noexcept = default;
+
+Form::Section& Form::AddSection(const std::string& title, const std::string& description)
+{
+ return m_Sections.emplace_back(title, description);
+}
+
+int Form::GetNumberOfSections() const
+{
+ return static_cast(m_Sections.size());
+}
+
+Form::Section& Form::GetSection(int index)
+{
+ return m_Sections.at(index);
+}
+
+const Form::Section& Form::GetSection(int index) const
+{
+ return m_Sections.at(index);
+}
+
+std::string Form::GetTitle() const
+{
+ return m_Sections[0].GetTitle();
+}
+
+void Form::SetTitle(const std::string& title)
+{
+ m_Sections[0].SetTitle(title);
+}
+
+std::string Form::GetDescription() const
+{
+ return m_Sections[0].GetDescription();
+}
+
+void Form::SetDescription(const std::string& description)
+{
+ m_Sections[0].SetDescription(description);
+}
+
+std::vector Form::GetQuestions() const
+{
+ return m_Sections[0].GetQuestions();
+}
+
+void Form::AddQuestion(Question* question)
+{
+ m_Sections[0].AddQuestion(question);
+}
+
+std::vector::const_iterator Form::begin() const
+{
+ return m_Sections.begin();
+}
+
+std::vector::const_iterator Form::end() const
+{
+ return m_Sections.end();
+}
+
+std::vector::iterator Form::begin()
+{
+ return m_Sections.begin();
+}
+
+std::vector::iterator Form::end()
+{
+ return m_Sections.end();
+}
+
+void mitk::Forms::SubmitToCSV(const Form& form, const fs::path& csvPath)
+{
+ std::ofstream csvFile;
+
+ if (fs::exists(csvPath))
+ {
+ csvFile.open(csvPath, std::ofstream::app);
+
+ if (!csvFile.is_open())
+ mitkThrow() << "Could not open file \"" << csvPath << "\"!";
+ }
+ else
+ {
+ csvFile.open(csvPath);
+
+ if (!csvFile.is_open())
+ mitkThrow() << "Could not create file \"" << csvPath << "\"!";
+
+ csvFile << "\"Timestamp\"";
+
+ for (const auto& section : form)
+ {
+ for (const auto* question : section.GetQuestions())
+ {
+ csvFile << ",\"" << question->GetQuestionText() << '"';
+ }
+ }
+
+ csvFile << '\n';
+ }
+
+ csvFile << '"' << GetCurrentISO8601DateTime() << '"';
+
+ for (const auto& section : form)
+ {
+ for (const auto* question : section.GetQuestions())
+ {
+ csvFile << ",\"";
+ bool isFirstResponse = true;
+
+ for (const auto& response : question->GetResponsesAsStrings())
+ {
+ if (!isFirstResponse)
+ csvFile << ';';
+
+ csvFile << response;
+
+ isFirstResponse = false;
+ }
+
+ csvFile << '"';
+ }
+ }
+
+ csvFile << std::endl;
+}
+
+void mitk::Forms::from_json(const nlohmann::ordered_json& j, Form& f)
+{
+ if (!j.contains("FileFormat") || j["FileFormat"] != "MITK Form")
+ mitkThrow() << "Expected \"FileFormat\" field to be \"MITK Form\"!";
+
+ if (!j.contains("Version") || j["Version"] != 1)
+ mitkThrow() << "Expected \"Version\" field to be 1!";;
+
+ const auto* questionFactory = IQuestionFactory::GetInstance();
+
+ if (j.contains("Sections"))
+ {
+ bool isFirstSection = true;
+
+ for (const auto& jSection : j["Sections"])
+ {
+ std::string title;
+ std::string description;
+
+ if (jSection.contains("Title"))
+ title = jSection["Title"];
+
+ if (jSection.contains("Description"))
+ description = jSection["Description"];
+
+ auto& section = isFirstSection
+ ? f.GetSection(0)
+ : f.AddSection(title, description);
+
+ if (isFirstSection)
+ {
+ section.SetTitle(title);
+ section.SetDescription(description);
+
+ isFirstSection = false;
+ }
+
+ if (jSection.contains("Questions"))
+ {
+ for (const auto& jQuestion : jSection["Questions"])
+ {
+ auto question = questionFactory->Create(jQuestion["Type"]);
+ question->FromJSON(jQuestion);
+
+ section.AddQuestion(question);
+ }
+ }
+ }
+ }
+}
+
+void mitk::Forms::to_json(nlohmann::ordered_json& j, const Form& f)
+{
+ j["FileFormat"] = "MITK Form";
+ j["Version"] = 1;
+
+ const int numberOfSections = f.GetNumberOfSections();
+
+ for (int index = 0; index < numberOfSections; ++index)
+ {
+ const auto& section = f.GetSection(index);
+ ordered_json jSection;
+
+ if (auto title = section.GetTitle(); !title.empty())
+ jSection["Title"] = title;
+
+ if (auto description = section.GetDescription(); !description.empty())
+ jSection["Description"] = description;
+
+ for (const auto* question : section.GetQuestions())
+ {
+ ordered_json jQuestion;
+ question->ToJSON(jQuestion);
+
+ jSection["Questions"].push_back(jQuestion);
+ }
+
+ j["Sections"].push_back(jSection);
+ }
+}
diff --git a/Modules/Forms/src/mitkIQuestionFactory.cpp b/Modules/Forms/src/mitkIQuestionFactory.cpp
new file mode 100644
index 0000000000..e3a6ab5a26
--- /dev/null
+++ b/Modules/Forms/src/mitkIQuestionFactory.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;
+
+IQuestionFactory* IQuestionFactory::GetInstance()
+{
+ static auto context = us::GetModuleContext();
+ static us::ServiceReference serviceReference;
+
+ if (!serviceReference)
+ serviceReference = context->GetServiceReference();
+
+ return serviceReference
+ ? context->GetService(serviceReference)
+ : nullptr;
+}
+
+IQuestionFactory::~IQuestionFactory() = default;
diff --git a/Modules/Forms/src/mitkLinearScaleQuestion.cpp b/Modules/Forms/src/mitkLinearScaleQuestion.cpp
new file mode 100644
index 0000000000..b2c55bc310
--- /dev/null
+++ b/Modules/Forms/src/mitkLinearScaleQuestion.cpp
@@ -0,0 +1,121 @@
+/*============================================================================
+
+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
+
+using namespace mitk::Forms;
+using namespace nlohmann;
+
+LinearScaleQuestion::LinearScaleQuestion()
+ : m_Range({1, 5})
+{
+}
+
+LinearScaleQuestion::~LinearScaleQuestion() = default;
+
+std::string LinearScaleQuestion::GetType() const
+{
+ return "Linear scale";
+}
+
+Question* LinearScaleQuestion::CreateAnother() const
+{
+ return new LinearScaleQuestion;
+}
+
+void LinearScaleQuestion::FromJSON(const nlohmann::ordered_json& j)
+{
+ from_json(j, *this);
+}
+
+void LinearScaleQuestion::ToJSON(nlohmann::ordered_json& j) const
+{
+ to_json(j, *this);
+}
+
+std::vector LinearScaleQuestion::GetResponsesAsStrings() const
+{
+ std::vector responses;
+
+ if (m_Response.has_value())
+ responses.push_back(std::to_string(m_Response.value()));
+
+ return responses;
+}
+
+void LinearScaleQuestion::ClearResponses()
+{
+ m_Response.reset();
+}
+
+bool LinearScaleQuestion::IsComplete() const
+{
+ return m_Response.has_value();
+}
+
+std::optional LinearScaleQuestion::GetResponse() const
+{
+ return m_Response;
+}
+
+void LinearScaleQuestion::SetResponse(int response)
+{
+ m_Response = response;
+}
+
+std::pair LinearScaleQuestion::GetRange() const
+{
+ return m_Range;
+}
+
+void LinearScaleQuestion::SetRange(const std::pair& range)
+{
+ if (range.first < 0 || range.first > 1)
+ mitkThrow() << "Invalid lower bound " << range.first << " in range: must be 0 or 1!";
+
+ if (range.second < 2 || range.second > 10)
+ mitkThrow() << "Invalid upper bound " << range.second << " in range: must lie between 2 and 10!";
+
+ m_Range = range;
+}
+
+std::pair LinearScaleQuestion::GetRangeLabels() const
+{
+ return m_Labels;
+}
+
+void LinearScaleQuestion::SetRangeLabels(const std::pair& labels)
+{
+ m_Labels = labels;
+}
+
+void mitk::Forms::from_json(const ordered_json& j, LinearScaleQuestion& q)
+{
+ from_json(j, static_cast(q));
+
+ if (j.contains("Range"))
+ q.SetRange(j["Range"]);
+
+ if (j.contains("Labels"))
+ q.SetRangeLabels(j["Labels"]);
+}
+
+void mitk::Forms::to_json(ordered_json& j, const LinearScaleQuestion& q)
+{
+ to_json(j, static_cast(q));
+
+ j["Range"] = q.GetRange();
+ j["Labels"] = q.GetRangeLabels();
+}
diff --git a/Modules/Forms/src/mitkModuleActivator.cpp b/Modules/Forms/src/mitkModuleActivator.cpp
new file mode 100644
index 0000000000..362454bb27
--- /dev/null
+++ b/Modules/Forms/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 "mitkQuestionFactory.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+using namespace mitk::Forms;
+
+ModuleActivator::ModuleActivator()
+ : m_QuestionFactory(std::make_unique())
+{
+}
+
+ModuleActivator::~ModuleActivator() = default;
+
+void ModuleActivator::Load(us::ModuleContext* context)
+{
+ context->RegisterService(m_QuestionFactory.get());
+
+ this->RegisterQuestion();
+ this->RegisterQuestion();
+ this->RegisterQuestion();
+ this->RegisterQuestion();
+ this->RegisterQuestion();
+ this->RegisterQuestion();
+}
+
+void ModuleActivator::Unload(us::ModuleContext*)
+{
+}
+
+US_EXPORT_MODULE_ACTIVATOR(ModuleActivator)
diff --git a/Modules/Forms/src/mitkModuleActivator.h b/Modules/Forms/src/mitkModuleActivator.h
new file mode 100644
index 0000000000..44f22607e2
--- /dev/null
+++ b/Modules/Forms/src/mitkModuleActivator.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 mitkModuleActivator_h
+#define mitkModuleActivator_h
+
+#include
+#include
+#include
+
+namespace mitk::Forms
+{
+ class ModuleActivator : public us::ModuleActivator
+ {
+ public:
+ ModuleActivator();
+ ~ModuleActivator() override;
+
+ void Load(us::ModuleContext* context) override;
+ void Unload(us::ModuleContext* context) override;
+
+ private:
+ template
+ void RegisterQuestion()
+ {
+ m_QuestionFactory->Register(new TQuestion);
+ }
+
+ std::unique_ptr m_QuestionFactory;
+ };
+}
+
+#endif
diff --git a/Modules/Forms/src/mitkMultipleChoiceQuestion.cpp b/Modules/Forms/src/mitkMultipleChoiceQuestion.cpp
new file mode 100644
index 0000000000..78ef357c4f
--- /dev/null
+++ b/Modules/Forms/src/mitkMultipleChoiceQuestion.cpp
@@ -0,0 +1,48 @@
+/*============================================================================
+
+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
+
+using namespace mitk::Forms;
+using namespace nlohmann;
+
+MultipleChoiceQuestion::~MultipleChoiceQuestion() = default;
+
+std::string MultipleChoiceQuestion::GetType() const
+{
+ return "Multiple choice";
+}
+
+Question* MultipleChoiceQuestion::CreateAnother() const
+{
+ return new MultipleChoiceQuestion;
+}
+
+void MultipleChoiceQuestion::FromJSON(const nlohmann::ordered_json& j)
+{
+ from_json(j, *this);
+}
+
+void MultipleChoiceQuestion::ToJSON(nlohmann::ordered_json& j) const
+{
+ to_json(j, *this);
+}
+
+void mitk::Forms::from_json(const ordered_json& j, MultipleChoiceQuestion& q)
+{
+ from_json(j, static_cast(q));
+}
+
+void mitk::Forms::to_json(ordered_json& j, const MultipleChoiceQuestion& q)
+{
+ to_json(j, static_cast(q));
+}
diff --git a/Modules/Forms/src/mitkParagraphQuestion.cpp b/Modules/Forms/src/mitkParagraphQuestion.cpp
new file mode 100644
index 0000000000..6348322d28
--- /dev/null
+++ b/Modules/Forms/src/mitkParagraphQuestion.cpp
@@ -0,0 +1,48 @@
+/*============================================================================
+
+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
+
+using namespace mitk::Forms;
+using namespace nlohmann;
+
+ParagraphQuestion::~ParagraphQuestion() = default;
+
+std::string ParagraphQuestion::GetType() const
+{
+ return "Paragraph";
+}
+
+Question* ParagraphQuestion::CreateAnother() const
+{
+ return new ParagraphQuestion;
+}
+
+void ParagraphQuestion::FromJSON(const nlohmann::ordered_json& j)
+{
+ from_json(j, *this);
+}
+
+void ParagraphQuestion::ToJSON(nlohmann::ordered_json& j) const
+{
+ to_json(j, *this);
+}
+
+void mitk::Forms::from_json(const ordered_json& j, ParagraphQuestion& q)
+{
+ from_json(j, static_cast(q));
+}
+
+void mitk::Forms::to_json(ordered_json& j, const ParagraphQuestion& q)
+{
+ to_json(j, static_cast(q));
+}
diff --git a/Modules/Forms/src/mitkQuestion.cpp b/Modules/Forms/src/mitkQuestion.cpp
new file mode 100644
index 0000000000..ffc9fba686
--- /dev/null
+++ b/Modules/Forms/src/mitkQuestion.cpp
@@ -0,0 +1,68 @@
+/*============================================================================
+
+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
+
+using namespace mitk::Forms;
+using namespace nlohmann;
+
+Question::Question()
+ : m_IsRequired(false)
+{
+}
+
+Question::~Question() = default;
+
+std::string Question::GetQuestionText() const
+{
+ return m_QuestionText;
+}
+
+void Question::SetQuestionText(const std::string& question)
+{
+ m_QuestionText = question;
+}
+
+void Question::SetRequired(bool required)
+{
+ m_IsRequired = required;
+}
+
+bool Question::IsRequired() const
+{
+ return m_IsRequired;
+}
+
+std::string Question::GetRequiredText() const
+{
+ return "This is a required question";
+}
+
+void mitk::Forms::from_json(const ordered_json& j, Question& q)
+{
+ if (j.contains("Text"))
+ q.SetQuestionText(j["Text"]);
+
+ if (j.contains("Required"))
+ q.SetRequired(j["Required"]);
+}
+
+void mitk::Forms::to_json(ordered_json& j, const Question& q)
+{
+ j["Text"] = q.GetQuestionText();
+
+ if (q.IsRequired())
+ j["Required"] = true;
+
+ j["Type"] = q.GetType();
+}
diff --git a/Modules/Forms/src/mitkQuestionFactory.cpp b/Modules/Forms/src/mitkQuestionFactory.cpp
new file mode 100644
index 0000000000..26e77969c7
--- /dev/null
+++ b/Modules/Forms/src/mitkQuestionFactory.cpp
@@ -0,0 +1,47 @@
+/*============================================================================
+
+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 "mitkQuestionFactory.h"
+
+#include
+#include
+
+using namespace mitk::Forms;
+
+QuestionFactory::QuestionFactory() = default;
+
+QuestionFactory::~QuestionFactory()
+{
+}
+
+void QuestionFactory::Register(Question* question)
+{
+ auto type = question->GetType();
+
+ if (m_Prototypes.find(type) != m_Prototypes.end())
+ mitkThrow() << "A prototype is already registered for questions of type \"" << type << "\"!";
+
+ m_Prototypes[type].reset(question);
+}
+
+Question* QuestionFactory::Create(const std::string& type) const
+{
+ try
+ {
+ return m_Prototypes.at(type)->CreateAnother();
+ }
+ catch (const std::out_of_range&)
+ {
+ }
+
+ return nullptr;
+}
diff --git a/Modules/Forms/src/mitkQuestionFactory.h b/Modules/Forms/src/mitkQuestionFactory.h
new file mode 100644
index 0000000000..30bbd418db
--- /dev/null
+++ b/Modules/Forms/src/mitkQuestionFactory.h
@@ -0,0 +1,38 @@
+/*============================================================================
+
+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 mitkQuestionFactory_h
+#define mitkQuestionFactory_h
+
+#include
+
+#include
+#include
+#include
+
+namespace mitk::Forms
+{
+ class QuestionFactory : public IQuestionFactory
+ {
+ public:
+ QuestionFactory();
+ ~QuestionFactory() override;
+
+ void Register(Question* question) override;
+ Question* Create(const std::string& type) const override;
+
+ private:
+ std::unordered_map> m_Prototypes;
+ };
+}
+
+#endif
diff --git a/Modules/Forms/src/mitkQuestionWithOptions.cpp b/Modules/Forms/src/mitkQuestionWithOptions.cpp
new file mode 100644
index 0000000000..101f44df8c
--- /dev/null
+++ b/Modules/Forms/src/mitkQuestionWithOptions.cpp
@@ -0,0 +1,84 @@
+/*============================================================================
+
+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
+
+using namespace mitk::Forms;
+using namespace nlohmann;
+
+QuestionWithOptions::~QuestionWithOptions() = default;
+
+size_t QuestionWithOptions::AddOption(const std::string& option)
+{
+ auto i = m_Options.size();
+ m_Options.push_back(option);
+ return i;
+}
+
+std::vector QuestionWithOptions::GetOptions() const
+{
+ return m_Options;
+}
+
+void QuestionWithOptions::AddResponse(size_t i)
+{
+ m_Responses.insert(i);
+}
+
+void QuestionWithOptions::RemoveResponse(size_t i)
+{
+ m_Responses.erase(i);
+}
+
+void QuestionWithOptions::SetResponse(size_t i)
+{
+ m_Responses = { i };
+}
+
+std::vector QuestionWithOptions::GetResponsesAsStrings() const
+{
+ std::vector responses;
+
+ for (const auto i : m_Responses)
+ responses.push_back(m_Options[i]);
+
+ return responses;
+}
+
+void QuestionWithOptions::ClearResponses()
+{
+ m_Responses.clear();
+}
+
+bool QuestionWithOptions::IsComplete() const
+{
+ return !m_Responses.empty();
+}
+
+void mitk::Forms::from_json(const ordered_json& j, QuestionWithOptions& q)
+{
+ from_json(j, static_cast(q));
+
+ if (j.contains("Options"))
+ {
+ for (const auto& option : j["Options"])
+ q.AddOption(option);
+ }
+}
+
+void mitk::Forms::to_json(ordered_json& j, const QuestionWithOptions& q)
+{
+ to_json(j, static_cast(q));
+
+ j["Options"] = q.GetOptions();
+}
diff --git a/Modules/Forms/src/mitkQuestionWithOtherOption.cpp b/Modules/Forms/src/mitkQuestionWithOtherOption.cpp
new file mode 100644
index 0000000000..eaa9f5ad97
--- /dev/null
+++ b/Modules/Forms/src/mitkQuestionWithOtherOption.cpp
@@ -0,0 +1,101 @@
+/*============================================================================
+
+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
+
+using namespace mitk::Forms;
+using namespace nlohmann;
+
+QuestionWithOtherOption::QuestionWithOtherOption()
+ : m_HasOtherOption(false)
+{
+}
+
+QuestionWithOtherOption::~QuestionWithOtherOption() = default;
+
+void QuestionWithOtherOption::EnableOtherOption()
+{
+ m_HasOtherOption = true;
+}
+
+bool QuestionWithOtherOption::HasOtherOption() const
+{
+ return m_HasOtherOption;
+}
+
+void QuestionWithOtherOption::SetResponse(size_t i)
+{
+ QuestionWithOptions::SetResponse(i);
+ m_OtherResponse.reset();
+}
+
+void QuestionWithOtherOption::AddOtherResponse(const std::string& response)
+{
+ if (!m_HasOtherOption)
+ mitkThrow() << "Adding \"Other\" response is disallowed!";
+
+ m_OtherResponse = response;
+}
+
+void QuestionWithOtherOption::RemoveOtherResponse()
+{
+ m_OtherResponse.reset();
+}
+
+void QuestionWithOtherOption::SetOtherResponse(const std::string& response)
+{
+ if (!m_HasOtherOption)
+ mitkThrow() << "Setting \"Other\" response is disallowed!";
+
+ QuestionWithOptions::ClearResponses();
+ m_OtherResponse = response;
+}
+
+std::vector QuestionWithOtherOption::GetResponsesAsStrings() const
+{
+ auto responses = QuestionWithOptions::GetResponsesAsStrings();
+
+ if (m_OtherResponse.has_value())
+ responses.push_back(m_OtherResponse.value());
+
+ return responses;
+}
+
+void QuestionWithOtherOption::ClearResponses()
+{
+ QuestionWithOptions::ClearResponses();
+ m_OtherResponse.reset();
+}
+
+bool QuestionWithOtherOption::IsComplete() const
+{
+ return QuestionWithOptions::IsComplete() || m_OtherResponse.has_value();
+}
+
+void mitk::Forms::from_json(const ordered_json& j, QuestionWithOtherOption& q)
+{
+ from_json(j, static_cast(q));
+
+ if (j.contains("Other") && j["Other"] == true)
+ q.EnableOtherOption();
+}
+
+void mitk::Forms::to_json(ordered_json& j, const QuestionWithOtherOption& q)
+{
+ to_json(j, static_cast(q));
+
+ if (q.HasOtherOption())
+ j["Other"] = true;
+}
diff --git a/Modules/Forms/src/mitkShortAnswerQuestion.cpp b/Modules/Forms/src/mitkShortAnswerQuestion.cpp
new file mode 100644
index 0000000000..9931e99c1e
--- /dev/null
+++ b/Modules/Forms/src/mitkShortAnswerQuestion.cpp
@@ -0,0 +1,48 @@
+/*============================================================================
+
+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
+
+using namespace mitk::Forms;
+using namespace nlohmann;
+
+ShortAnswerQuestion::~ShortAnswerQuestion() = default;
+
+std::string ShortAnswerQuestion::GetType() const
+{
+ return "Short answer";
+}
+
+Question* ShortAnswerQuestion::CreateAnother() const
+{
+ return new ShortAnswerQuestion;
+}
+
+void ShortAnswerQuestion::FromJSON(const nlohmann::ordered_json& j)
+{
+ from_json(j, *this);
+}
+
+void ShortAnswerQuestion::ToJSON(nlohmann::ordered_json& j) const
+{
+ to_json(j, *this);
+}
+
+void mitk::Forms::from_json(const ordered_json& j, ShortAnswerQuestion& q)
+{
+ from_json(j, static_cast(q));
+}
+
+void mitk::Forms::to_json(ordered_json& j, const ShortAnswerQuestion& q)
+{
+ to_json(j, static_cast(q));
+}
diff --git a/Modules/Forms/src/mitkTextQuestion.cpp b/Modules/Forms/src/mitkTextQuestion.cpp
new file mode 100644
index 0000000000..4b538eb262
--- /dev/null
+++ b/Modules/Forms/src/mitkTextQuestion.cpp
@@ -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.
+
+============================================================================*/
+
+#include
+
+using namespace mitk::Forms;
+
+TextQuestion::~TextQuestion() = default;
+
+std::vector TextQuestion::GetResponsesAsStrings() const
+{
+ return { m_Response };
+}
+
+void TextQuestion::ClearResponses()
+{
+ m_Response.clear();
+}
+
+bool TextQuestion::IsComplete() const
+{
+ return !m_Response.empty();
+}
+
+std::string TextQuestion::GetResponse() const
+{
+ return m_Response;
+}
+
+void TextQuestion::SetResponse(const std::string& response)
+{
+ m_Response = response;
+}
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..28b61f1500
--- /dev/null
+++ b/Modules/FormsUI/include/QmitkQuestionWidget.h
@@ -0,0 +1,160 @@
+/*============================================================================
+
+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;
+}
+
+/** \brief Abstract base class for all types of question widgets used in a QmitkForm.
+ *
+ * This class manages GUI elements common to all questions like labels for the question text and
+ * a reminder that a response might be required. All specific GUI elements of derived classes
+ * must be put into a layout and inserted by calling InsertLayout().
+ *
+ * Please make sure to read the full documentation of the pure virtual functions in particular to
+ * fully understand implications and requirements.
+ */
+class MITKFORMSUI_EXPORT QmitkQuestionWidget : public QFrame
+{
+ Q_OBJECT
+
+public:
+ explicit QmitkQuestionWidget(QWidget* parent = nullptr);
+ ~QmitkQuestionWidget() override;
+
+ /** \name Pure virtual functions
+ *
+ * QmitkQuestionWidget is an abstract base class. Derive from this class to add a widget for a
+ * certain type of Question and override the following pure virtual functions. Please read the
+ * full documentation for all of these functions to fully understand implications and requirements.
+ *
+ * Do not forget to register any new question widget by calling mitk::Forms::UI::IQuestionWidgetFactory::Register()
+ * like it is done in this module's activator class.
+ *
+ * \sa mitk::Forms::UI::IQuestionWidgetFactory
+ * \{
+ */
+
+ /** \brief Create a new instance of the derived question widget class type.
+ *
+ * This method is mainly used by mitk::Forms::UI::IQuestionWidgetFactory to create
+ * new instances from registered prototype instances.
+ *
+ * \code{.cpp}
+ * QmitkQuestionWidget* QmitkRhetoricalQuestionWidget::CreateAnother(QWidget* parent) const
+ * {
+ * return new RhetoricalQuestionWidget(parent);
+ * }
+ * \endcode
+ */
+ virtual QmitkQuestionWidget* CreateAnother(QWidget* parent = nullptr) const = 0;
+
+ /** \brief Get the question associated with this widget.
+ *
+ * \code{.cpp}
+ * Question* QmitkRhetoricalQuestionWidget::GetQuestion() const
+ * {
+ * // In class declaration: mitk::Forms::RhetoricalQuestion* m_Question;
+ * return m_Question;
+ * }
+ * \endcode
+ *
+ * \sa SetQuestion()
+ */
+ virtual mitk::Forms::Question* GetQuestion() const = 0;
+
+ /** \brief Initialize the widget based on the given question.
+ *
+ * This method is rarely used explicitly since it is automatically called by
+ * mitk::Forms::UI::IQuestionWidgetFactory::Create().
+ *
+ * You are excepted to throw an mitk::Exception if the Question type does not match
+ * the expectations of the widget.
+ *
+ * \note It is required to call this base class method at the beginning of the derived
+ * method.
+ *
+ * \code{.cpp}
+ * void QmitkRhetoricalQuestionWidget::SetQuestion(Question* question)
+ * {
+ * QmitkQuestionWidget::SetQuestion(question);
+ *
+ * auto rhetoricalQuestion = dynamic_cast(question);
+ *
+ * if (rhetoricalQuestion == nullptr)
+ * mitkThrow() << "QmitkRhetoricalQuestionWidget only accepts RhetoricalQuestion as question type!";
+ *
+ * m_Question = rhetoricalQuestion;
+ * }
+ * \endcode
+ */
+ virtual void SetQuestion(mitk::Forms::Question* question) = 0;
+
+ /** \brief Reset the state of the GUI as if no interaction would have been happened yet.
+ *
+ * \note It is required to call this base class method at the end of the derived method.
+ *
+ * \code{.cpp}
+ * void QmitkRhetoricalQuestionWidget::Reset()
+ * {
+ * // It's a rhetorical question... nothing specific to be reset.
+ * QmitkQuestionWidget::Reset(); // Nevertheless, this is required at the end!
+ * }
+ * \endcode
+ */
+ virtual void Reset() = 0;
+
+ /**\}*/
+
+ void SetRequirementVisible(bool visible);
+ void ShowRequirement();
+ void HideRequirement();
+
+protected:
+ /** \brief Insert a layout containing all GUI elements specific to the derived question widget type.
+ *
+ * This method is typically called from the constructor of a derived class after all GUI elements
+ * have been set up and organized in a layout.
+ *
+ * \code{.cpp}
+ * QmitkRhetoricalQuestionWidget::QmitkRhetoricalQuestionWidget(QWidget* parent)
+ * : QmitkQuestionWidget(parent),
+ * m_Question(nullptr),
+ * m_Layout(new QVBoxLayout),
+ * m_Label(new QLabel)
+ * {
+ * m_Label->setText("You know the answer... we all do.");
+ * m_Layout->addWidget(m_Label);
+ *
+ * this->InsertLayout(m_Layout);
+ * }
+ * \endcode
+ */
+ 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..284946de2d
--- /dev/null
+++ b/Modules/FormsUI/include/mitkIQuestionWidgetFactory.h
@@ -0,0 +1,65 @@
+/*============================================================================
+
+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
+ {
+ /** \brief Register widgets for questions.
+ *
+ * This is a service interface. Obtain a pointer to its single instance via GetInstance().
+ *
+ * Each QmitkQuestionWidget subclass must be registered by calling Register(), which is typically done
+ * in the module activator class. After QmitkQuestionWidget subclasses are registered, instances of them
+ * can be created with Create() based on their matching type of Question.
+ */
+ class MITKFORMSUI_EXPORT IQuestionWidgetFactory
+ {
+ public:
+ /** \brief Obtain a pointer to the single instance of this service.
+ */
+ static IQuestionWidgetFactory* GetInstance();
+
+ virtual ~IQuestionWidgetFactory();
+
+ /** \brief Register a QmitkQuestionWidget subclass for a certain Question type string.
+ *
+ * The service takes over ownership of the passed QmitkQuestionWidget pointer.
+ *
+ * \sa Question::GetType()
+ */
+ virtual void Register(const std::string& questionType, QmitkQuestionWidget* widgetPrototype) = 0;
+
+ /** \brief Create an instance of a matching QmitkQuestionWidget subclass for a certain question.
+ *
+ * The given question is passed to QmitkQuestionWidget::SetQuestion().
+ *
+ * \sa QmitkQuestionWidget::CreateAnother()
+ */
+ 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..8d9c7c9e37
--- /dev/null
+++ b/Modules/FormsUI/src/QmitkForm.cpp
@@ -0,0 +1,339 @@
+/*============================================================================
+
+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
+ {
+ mitk::Forms::SubmitToCSV(m_Form, m_ResponsesPath);
+ }
+ catch (const mitk::Exception& e)
+ {
+ QMessageBox messageBox;
+ messageBox.setWindowTitle("Submit form");
+ messageBox.setIcon(QMessageBox::Warning);
+ messageBox.setText(e.GetDescription());
+ auto retryButton = messageBox.addButton("Retry", QMessageBox::NoRole);
+ messageBox.addButton("Ignore", QMessageBox::YesRole);
+
+ messageBox.exec();
+
+ if (messageBox.clickedButton() == retryButton)
+ retry = true;
+ }
+ } while (retry);
+
+ m_HasBeenSubmitted = true;
+ this->Update();
+ }
+ }
+}
+
+void QmitkForm::OnClearButtonClicked()
+{
+ QMessageBox messageBox;
+ messageBox.setWindowTitle("Clear form?");
+ messageBox.setIcon(QMessageBox::Question);
+ messageBox.setText("This will remove your answers from all\nquestions and cannot be undone.");
+ messageBox.addButton("Cancel", QMessageBox::NoRole);
+ auto clearFormButton = messageBox.addButton("Clear form", QMessageBox::YesRole);
+
+ messageBox.exec();
+
+ if (messageBox.clickedButton() == clearFormButton)
+ 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..4f45c01d85
--- /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