diff --git a/CMakeExternals/CTK.cmake b/CMakeExternals/CTK.cmake index 8bb9152ab8..9861ce9181 100644 --- a/CMakeExternals/CTK.cmake +++ b/CMakeExternals/CTK.cmake @@ -1,107 +1,107 @@ #----------------------------------------------------------------------------- # CTK #----------------------------------------------------------------------------- if(MITK_USE_CTK) # Sanity checks if(DEFINED CTK_DIR AND NOT EXISTS ${CTK_DIR}) message(FATAL_ERROR "CTK_DIR variable is defined but corresponds to non-existing directory") endif() set(proj CTK) set(proj_DEPENDENCIES DCMTK) set(CTK_DEPENDS ${proj}) if(NOT DEFINED CTK_DIR) - set(revision_tag 6c0eddaa) + set(revision_tag b93e8dca) set(ctk_optional_cache_args ) if(MITK_USE_Python) list(APPEND ctk_optional_cache_args -DCTK_LIB_Scripting/Python/Widgets:BOOL=ON -DCTK_ENABLE_Python_Wrapping:BOOL=OFF - -DCTK_APP_ctkSimplePythonShell:BOOL=ON + -DCTK_APP_ctkSimplePythonShell:BOOL=OFF "-DPYTHON_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE}" "-DPYTHON_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR}" "-DPYTHON_LIBRARY:FILEPATH=${Python3_LIBRARY}" ) else() list(APPEND ctk_optional_cache_args -DCTK_LIB_Scripting/Python/Widgets:BOOL=OFF -DCTK_ENABLE_Python_Wrapping:BOOL=OFF -DCTK_APP_ctkSimplePythonShell:BOOL=OFF ) endif() if(NOT MITK_USE_Python) list(APPEND ctk_optional_cache_args -DDCMTK_CMAKE_DEBUG_POSTFIX:STRING=d ) endif() if(CTEST_USE_LAUNCHERS) list(APPEND ctk_optional_cache_args "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" ) endif() FOREACH(type RUNTIME ARCHIVE LIBRARY) IF(DEFINED CTK_PLUGIN_${type}_OUTPUT_DIRECTORY) LIST(APPEND mitk_optional_cache_args -DCTK_PLUGIN_${type}_OUTPUT_DIRECTORY:PATH=${CTK_PLUGIN_${type}_OUTPUT_DIRECTORY}) ENDIF() ENDFOREACH() mitk_query_custom_ep_vars() ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} URL ${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/CTK_${revision_tag}.tar.gz - URL_MD5 c8025c0009d5dd207cd442eadca409b4 + URL_MD5 C86289F25E79B0C5C12E424BD706388F UPDATE_COMMAND "" INSTALL_COMMAND "" CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} ${ctk_optional_cache_args} # The CTK PluginFramework cannot cope with # a non-empty CMAKE_DEBUG_POSTFIX for the plugin # libraries yet. -DCMAKE_DEBUG_POSTFIX:STRING= -DCTK_QT_VERSION:STRING=5 -DQt5_DIR=${Qt5_DIR} -DGit_EXECUTABLE:FILEPATH=${GIT_EXECUTABLE} -DGIT_EXECUTABLE:FILEPATH=${GIT_EXECUTABLE} -DCTK_BUILD_QTDESIGNER_PLUGINS:BOOL=OFF -DCTK_LIB_CommandLineModules/Backend/LocalProcess:BOOL=ON -DCTK_LIB_CommandLineModules/Frontend/QtGui:BOOL=ON -DCTK_LIB_PluginFramework:BOOL=ON -DCTK_LIB_DICOM/Widgets:BOOL=ON -DCTK_LIB_XNAT/Core:BOOL=ON -DCTK_PLUGIN_org.commontk.eventadmin:BOOL=ON -DCTK_PLUGIN_org.commontk.configadmin:BOOL=ON -DCTK_USE_GIT_PROTOCOL:BOOL=OFF -DDCMTK_DIR:PATH=${DCMTK_DIR} - -DPythonQt_URL:STRING=${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/PythonQt_e39be131.tar.gz # From https://github.com/kislinsk/PythonQt.git + -DPythonQt_URL:STRING=${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/PythonQt_fae23012.tar.gz ${${proj}_CUSTOM_CMAKE_ARGS} CMAKE_CACHE_ARGS ${ep_common_cache_args} ${${proj}_CUSTOM_CMAKE_CACHE_ARGS} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} ${${proj}_CUSTOM_CMAKE_CACHE_DEFAULT_ARGS} DEPENDS ${proj_DEPENDENCIES} ) ExternalProject_Get_Property(${proj} binary_dir) set(CTK_DIR ${binary_dir}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif() diff --git a/Modules/CommandLine/include/mitkCommandLineParser.h b/Modules/CommandLine/include/mitkCommandLineParser.h index b8a9e98d2e..07557ca89f 100644 --- a/Modules/CommandLine/include/mitkCommandLineParser.h +++ b/Modules/CommandLine/include/mitkCommandLineParser.h @@ -1,393 +1,393 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ /*========================================================================= Library: CTK Copyright (c) Kitware Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.txt Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. =========================================================================*/ #ifndef __mitkCommandLineParser_h #define __mitkCommandLineParser_h #include #include #include #include /** * * The MITK command line parser, based on the CTK command line parser. * * Use this class to add information about the command line arguments * your program understands and to easily parse them from a given list * of strings. * * This parser provides the following features: * *
    *
  • Add arguments by supplying a long name and/or a short name. * Arguments are validated using a regular expression. They can have * a default value and a help string.
  • *
  • Deprecated arguments.
  • *
  • Custom regular expressions for argument validation.
  • *
  • Set different argument name prefixes for native platform look and feel.
  • *
  • Create a help text for the command line arguments with support for * grouping arguments.
  • *
* * The main difference between the MITK command line parser and the CTK command line * parser is that the former does not depend on Qt. Apart from that an image type was * added and XML output improved for automatic GUI generation. * * std::out is used for output to keep dependencies to a minimum. */ class MITKCOMMANDLINE_EXPORT mitkCommandLineParser { public: enum Type { String = 0, Bool = 1, StringList = 2, Int = 3, Float = 4, Directory = 5, File = 6, Image = 7 }; enum Channel { None = 0, Input = 1, Output = 2 }; typedef std::vector StringContainerType; mitkCommandLineParser(); ~mitkCommandLineParser(); /** * Parse a given list of command line arguments. * * This method parses a list of string elements considering the known arguments * added by calls to addArgument(). If any one of the argument * values does not match the corresponding regular expression, * ok is set to false and an empty map object is returned. * * The keys in the returned map object correspond to the long argument string, * if it is not empty. Otherwise, the short argument string is used as key. The * us::Any values can safely be converted to the type specified in the * addArgument() method call. * * @param arguments A StringContainerType containing command line arguments. * @param ok A pointer to a boolean variable. Will be set to true * if all regular expressions matched, false otherwise. * @return A map object mapping the long argument (if empty, the short one) * to a us::Any containing the value. */ std::map parseArguments(const StringContainerType &arguments, bool *ok = nullptr); /** * Convenient method allowing to parse a given list of command line arguments. * @see parseArguments(const StringContainerType &, bool*) */ std::map parseArguments(int argc, char **argv, bool *ok = nullptr); /** * Returns a detailed error description if a call to parseArguments() * failed. * * @return The error description, empty if no error occured. * @see parseArguments(const StringContainerType&, bool*) */ std::string errorString() const; /** * This method returns all unparsed arguments, i.e. all arguments * for which no long or short name has been registered via a call * to addArgument(). * * @see addArgument() * * @return A list containing unparsed arguments. */ const StringContainerType &unparsedArguments() const; /** * Checks if the given argument has been added via a call * to addArgument(). * * @see addArgument() * * @param argument The argument to be checked. * @return true if the argument was added, false * otherwise. */ bool argumentAdded(const std::string &argument) const; /** * Checks if the given argument has been parsed successfully by a previous * call to parseArguments(). * * @param argument The argument to be checked. * @return true if the argument was parsed, false * otherwise. */ bool argumentParsed(const std::string &argument) const; /** * Adds a command line argument. An argument can have a long name * (like --long-argument-name), a short name (like -l), or both. The type * of the argument can be specified by using the type parameter. * The following types are supported: * * * * * * * * *
Type# of parametersDefault regular exprExample
us::Any::String1.*--test-string StringParameter
us::Any::Bool0does not apply--enable-something
us::Any::StringList-1.*--test-list string1 string2
us::Any::Int1-?[0-9]+--test-int -5
* * The regular expressions are used to validate the parameters of command line * arguments. You can restrict the valid set of parameters by calling * setExactMatchRegularExpression() for your argument. * * Optionally, a help string and a default value can be provided for the argument. If * the us::Any type of the default value does not match type, an * exception is thrown. Arguments with default values are always returned by * parseArguments(). * * You can also declare an argument deprecated, by setting deprecated * to true. Alternatively you can add a deprecated argument by calling * addDeprecatedArgument(). * * If the long or short argument has already been added, or if both are empty strings, * the method call has no effect. * * @param longarg The long argument name. * @param shortarg The short argument name. * @param type The argument type (see the list above for supported types). * @param argLabel The label of this argument, when auto generated interface is used. * @param argHelp A help string describing the argument. * @param defaultValue A default value for the argument. * @param ignoreRest All arguments after the current one will be ignored. * @param deprecated Declares the argument deprecated. * * @see setExactMatchRegularExpression() * @see addDeprecatedArgument() * @throws std::logic_error If the us::Any type of defaultValue * does not match type, a std::logic_error is thrown. */ void addArgument(const std::string &longarg, const std::string &shortarg, Type type, const std::string &argLabel, const std::string &argHelp = std::string(), const us::Any &defaultValue = us::Any(), bool optional = true, bool ignoreRest = false, bool deprecated = false, mitkCommandLineParser::Channel channel = mitkCommandLineParser::Channel::None); /** * Adds a deprecated command line argument. If a deprecated argument is provided * on the command line, argHelp is displayed in the console and * processing continues with the next argument. * * Deprecated arguments are grouped separately at the end of the help text * returned by helpText(). * * @param longarg The long argument name. * @param shortarg The short argument name. * @param argHelp A help string describing alternatives to the deprecated argument. */ void addDeprecatedArgument(const std::string &longarg, const std::string &shortarg, const std::string &argLabel, const std::string &argHelp); /** * Returns the vector of current Command line Parameter * */ std::vector < std::map > getArgumentList(); /** * Sets a custom regular expression for validating argument parameters. The method * errorString() can be used the get the last error description. * * @param argument The previously added long or short argument name. * @param expression A regular expression which the arugment parameters must match. * @param exactMatchFailedMessage An error message explaining why the parameter did * not match. * * @return true if the argument was found and the regular expression was set, * false otherwise. * * @see errorString() */ bool setExactMatchRegularExpression(const std::string &argument, const std::string &expression, const std::string &exactMatchFailedMessage); /** * The field width for the argument names without the help text. * * @return The argument names field width in the help text. */ std::string::size_type fieldWidth() const; /** * Creates a help text containing properly formatted argument names and help strings * provided by calls to addArgument(). The arguments can be grouped by * using beginGroup() and endGroup(). * * @param charPad The padding character. * @return The formatted help text. */ std::string helpText() const; /** * Sets the argument prefix for long and short argument names. This can be used * to create native command line arguments without changing the calls to * addArgument(). For example on Unix-based systems, long argument * names start with "--" and short names with "-", while on Windows argument names * always start with "/". * * Note that all methods in mitkCommandLineParser which take an argument name * expect the name as it was supplied to addArgument. * * Example usage: * * \code * ctkCommandLineParser parser; * parser.setArgumentPrefix("--", "-"); * parser.addArgument("long-argument", "l", us::Any::String); * StringContainerType args; * args << "program name" << "--long-argument Hi"; * parser.parseArguments(args); * \endcode * * @param longPrefix The prefix for long argument names. * @param shortPrefix The prefix for short argument names. */ void setArgumentPrefix(const std::string &longPrefix, const std::string &shortPrefix); /** * Begins a new group for documenting arguments. All newly added arguments via * addArgument() will be put in the new group. You can close the * current group by calling endGroup() or be opening a new group. * * Note that groups cannot be nested and all arguments which do not belong to * a group will be listed at the top of the text created by helpText(). * * @param description The description of the group */ void beginGroup(const std::string &description); /** * Ends the current group. * * @see beginGroup(const std::string&) */ void endGroup(); /** * Can be used to teach the parser to stop parsing the arguments and return False when * an unknown argument is encountered. By default StrictMode is disabled. * * @see parseArguments(const StringContainerType &, bool*) */ void setStrictModeEnabled(bool strictMode); /** * Is used to generate an XML output for any commandline program. */ void generateXmlOutput(); /** * Is used to set the title of the auto generated interface. * * @param title The title of the app. */ void setTitle(std::string title); /** * Is used to set the contributor for the help view in the auto generated interface. * * @param contributor Contributor of the app. */ void setContributor(std::string contributor); /** * Is used to categorize the apps in the commandline module. * * @param category The category of the app. */ void setCategory(std::string category); /** * Is used as the help text in the auto generated interface. * * @param description A short description for the app. */ void setDescription(std::string description); /** * Is used to group several Parameters in one groupbox in the auto generated interface. * Default name is "Parameters", with the tooltip: "Groupbox containing parameters." * * To change the group of several arguments, call this method before the arguments are added. * * @param name The name of the groupbox. * @param tooltip The tooltip of the groupbox. */ void changeParameterGroup(std::string name, std::string tooltip); -private: +protected: class ctkInternal; ctkInternal *Internal; std::string Title; std::string Contributor; std::string Category; std::string Description; std::string ParameterGroupName; std::string ParameterGroupDescription; }; #endif diff --git a/Modules/CommandLine/src/mitkCommandLineParser.cpp b/Modules/CommandLine/src/mitkCommandLineParser.cpp index 30af7acf7b..a5b2f1075c 100644 --- a/Modules/CommandLine/src/mitkCommandLineParser.cpp +++ b/Modules/CommandLine/src/mitkCommandLineParser.cpp @@ -1,1060 +1,1060 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ /*========================================================================= Library: CTK Copyright (c) Kitware Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.txt Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. =========================================================================*/ // STL includes #include #include // MITK includes #include "mitkCommandLineParser.h" using namespace std; namespace { // -------------------------------------------------------------------------- class CommandLineParserArgumentDescription { public: CommandLineParserArgumentDescription(const string &longArg, const string &longArgPrefix, const string &shortArg, const string &shortArgPrefix, mitkCommandLineParser::Type type, const string &argHelp, const string &argLabel, const us::Any &defaultValue, bool ignoreRest, bool deprecated, bool optional, mitkCommandLineParser::Channel channel, string &argGroup, string &groupDescription) : LongArg(longArg), LongArgPrefix(longArgPrefix), ShortArg(shortArg), ShortArgPrefix(shortArgPrefix), ArgHelp(argHelp), ArgLabel(argLabel), ArgGroup(argGroup), ArgGroupDescription(groupDescription), IgnoreRest(ignoreRest), NumberOfParametersToProcess(0), Deprecated(deprecated), Optional(optional), Channel(channel), DefaultValue(defaultValue), Value(type), ValueType(type) { Value = defaultValue; switch (type) { case mitkCommandLineParser::String: { NumberOfParametersToProcess = 1; } break; case mitkCommandLineParser::Bool: { NumberOfParametersToProcess = 0; } break; case mitkCommandLineParser::StringList: { NumberOfParametersToProcess = -1; } break; case mitkCommandLineParser::Int: { NumberOfParametersToProcess = 1; } break; case mitkCommandLineParser::Float: { NumberOfParametersToProcess = 1; } break; case mitkCommandLineParser::Directory: { NumberOfParametersToProcess = 1; } break; case mitkCommandLineParser::File: { NumberOfParametersToProcess = 1; } break; case mitkCommandLineParser::Image: { NumberOfParametersToProcess = 1; } break; default: std::cout << "Type not supported: " << static_cast(type); } } ~CommandLineParserArgumentDescription() {} bool addParameter(const string &value); string helpText(); string LongArg; string LongArgPrefix; string ShortArg; string ShortArgPrefix; string ArgHelp; string ArgLabel; string ArgGroup; string ArgGroupDescription; bool IgnoreRest; int NumberOfParametersToProcess; bool Deprecated; bool Optional; mitkCommandLineParser::Channel Channel; us::Any DefaultValue; us::Any Value; mitkCommandLineParser::Type ValueType; }; // -------------------------------------------------------------------------- bool CommandLineParserArgumentDescription::addParameter(const string &value) { switch (ValueType) { case mitkCommandLineParser::String: { Value = value; } break; case mitkCommandLineParser::Bool: { if (value.compare("true") == 0) Value = true; else Value = false; } break; case mitkCommandLineParser::StringList: { try { mitkCommandLineParser::StringContainerType list = us::any_cast(Value); list.push_back(value); Value = list; } catch (...) { mitkCommandLineParser::StringContainerType list; list.push_back(value); Value = list; } } break; case mitkCommandLineParser::Int: { stringstream ss(value); int i; ss >> i; Value = i; } break; case mitkCommandLineParser::Float: { stringstream ss(value); float f; ss >> f; Value = f; } break; case mitkCommandLineParser::Directory: case mitkCommandLineParser::Image: case mitkCommandLineParser::File: { Value = value; } break; default: return false; } return true; } // -------------------------------------------------------------------------- string CommandLineParserArgumentDescription::helpText() { string text; string shortAndLongArg; if (!this->ShortArg.empty()) { shortAndLongArg = " "; shortAndLongArg += this->ShortArgPrefix; shortAndLongArg += this->ShortArg; } if (!this->LongArg.empty()) { if (this->ShortArg.empty()) shortAndLongArg.append(" "); else shortAndLongArg.append(", "); shortAndLongArg += this->LongArgPrefix; shortAndLongArg += this->LongArg; } text = text + shortAndLongArg + ", " + this->ArgHelp; if (this->Optional) text += " (optional)"; if (!this->DefaultValue.Empty()) { if (this->ValueType == 1) { if (this->DefaultValue.ToString() == "0") text = text + ", (default: false)"; else text = text + ", (default: true)"; } else text = text + ", (default: " + this->DefaultValue.ToString() + ")"; } string value_type = "Unknown"; switch (this->ValueType) { case 0: { value_type = "String"; break; } case 1: { value_type = "Bool"; break; } case 2: { value_type = "StringList"; break; } case 3: { value_type = "Int"; break; } case 4: { value_type = "Float"; break; } case 5: { value_type = "Directory"; break; } case 6: { value_type = "File"; break; } case 7: { value_type = "Image"; } } text = text + ", Type: " + value_type; if (this->Channel == mitkCommandLineParser::Input) text = text + ", Channel: input"; else if (this->Channel == mitkCommandLineParser::Output) text = text + ", Channel: output"; text += "\n"; return text; } } // -------------------------------------------------------------------------- // ctkCommandLineParser::ctkInternal class // -------------------------------------------------------------------------- class mitkCommandLineParser::ctkInternal { public: ctkInternal() : Debug(false), FieldWidth(0), StrictMode(false) {} ~ctkInternal() {} CommandLineParserArgumentDescription *argumentDescription(const string &argument); vector ArgumentDescriptionList; map ArgNameToArgumentDescriptionMap; map> GroupToArgumentDescriptionListMap; StringContainerType UnparsedArguments; StringContainerType ProcessedArguments; string ErrorString; bool Debug; string::size_type FieldWidth; string LongPrefix; string ShortPrefix; string CurrentGroup; string DisableQSettingsLongArg; string DisableQSettingsShortArg; bool StrictMode; }; // -------------------------------------------------------------------------- // ctkCommandLineParser::ctkInternal methods // -------------------------------------------------------------------------- CommandLineParserArgumentDescription *mitkCommandLineParser::ctkInternal::argumentDescription(const string &argument) { string unprefixedArg = argument; if (!LongPrefix.empty() && argument.compare(0, LongPrefix.size(), LongPrefix) == 0) { // Case when (ShortPrefix + UnPrefixedArgument) matches LongPrefix if (argument == LongPrefix && !ShortPrefix.empty() && argument.compare(0, ShortPrefix.size(), ShortPrefix) == 0) { unprefixedArg = argument.substr(ShortPrefix.size(), argument.size()); } else { unprefixedArg = argument.substr(LongPrefix.size(), argument.size()); } } else if (!ShortPrefix.empty() && argument.compare(0, ShortPrefix.size(), ShortPrefix) == 0) { unprefixedArg = argument.substr(ShortPrefix.size(), argument.size()); } else if (!LongPrefix.empty() && !ShortPrefix.empty()) { return nullptr; } if (ArgNameToArgumentDescriptionMap.count(unprefixedArg)) { return this->ArgNameToArgumentDescriptionMap[unprefixedArg]; } return nullptr; } // -------------------------------------------------------------------------- // ctkCommandLineParser methods // -------------------------------------------------------------------------- mitkCommandLineParser::mitkCommandLineParser() { this->Internal = new ctkInternal(); this->Category = string(); this->Title = string(); this->Contributor = string(); this->Description = string(); this->ParameterGroupName = "Parameters"; this->ParameterGroupDescription = "Parameters"; } // -------------------------------------------------------------------------- mitkCommandLineParser::~mitkCommandLineParser() { delete this->Internal; } // -------------------------------------------------------------------------- map mitkCommandLineParser::parseArguments(const StringContainerType &arguments, bool *ok) { // Reset this->Internal->UnparsedArguments.clear(); this->Internal->ProcessedArguments.clear(); this->Internal->ErrorString.clear(); // foreach (CommandLineParserArgumentDescription* desc, this->Internal->ArgumentDescriptionList) for (unsigned int i = 0; i < Internal->ArgumentDescriptionList.size(); i++) { CommandLineParserArgumentDescription *desc = Internal->ArgumentDescriptionList.at(i); desc->Value = us::Any(desc->ValueType); if (!desc->DefaultValue.Empty()) { desc->Value = desc->DefaultValue; } } bool error = false; bool ignoreRest = false; CommandLineParserArgumentDescription *currentArgDesc = nullptr; vector parsedArgDescriptions; for (unsigned int i = 1; i < arguments.size(); ++i) { string argument = arguments.at(i); if (this->Internal->Debug) { std::cout << "Processing" << argument; } if (!argument.compare("--version")) { std::cout << "Git commit hash: " << MITK_REVISION << std::endl; - std::cout << "Git branch name: " << MITK_REVISION_NAME << std::endl; + std::cout << "Git branch name: " << MITK_REVISION_NAME << "\n" << std::endl; } if (!argument.compare("--xml") || !argument.compare("-xml") || !argument.compare("--XML") || !argument.compare("-XML")) { this->generateXmlOutput(); return map(); } // should argument be ignored ? if (ignoreRest) { if (this->Internal->Debug) { std::cout << " Skipping: IgnoreRest flag was been set"; } this->Internal->UnparsedArguments.push_back(argument); continue; } // Skip if the argument does not start with the defined prefix if (!(argument.compare(0, Internal->LongPrefix.size(), Internal->LongPrefix) == 0 || argument.compare(0, Internal->ShortPrefix.size(), Internal->ShortPrefix) == 0)) { if (this->Internal->StrictMode) { this->Internal->ErrorString = "Unknown argument "; this->Internal->ErrorString += argument; error = true; break; } if (this->Internal->Debug) { std::cout << " Skipping: It does not start with the defined prefix"; } this->Internal->UnparsedArguments.push_back(argument); continue; } // Skip if argument has already been parsed ... bool alreadyProcessed = false; for (auto alreadyHandledArgument : Internal->ProcessedArguments) if (argument.compare(alreadyHandledArgument) == 0) { alreadyProcessed = true; break; } if (alreadyProcessed) { if (this->Internal->StrictMode) { this->Internal->ErrorString = "Argument "; this->Internal->ErrorString += argument; this->Internal->ErrorString += " already processed !"; error = true; break; } if (this->Internal->Debug) { std::cout << " Skipping: Already processed !"; } continue; } // Retrieve corresponding argument description currentArgDesc = this->Internal->argumentDescription(argument); // Is there a corresponding argument description ? if (currentArgDesc) { // If the argument is deprecated, print the help text but continue processing if (currentArgDesc->Deprecated) { std::cout << "Deprecated argument " << argument << ": " << currentArgDesc->ArgHelp; } else { parsedArgDescriptions.push_back(currentArgDesc); } this->Internal->ProcessedArguments.push_back(currentArgDesc->ShortArg); this->Internal->ProcessedArguments.push_back(currentArgDesc->LongArg); int numberOfParametersToProcess = currentArgDesc->NumberOfParametersToProcess; ignoreRest = currentArgDesc->IgnoreRest; if (this->Internal->Debug && ignoreRest) { std::cout << " IgnoreRest flag is True"; } // Is the number of parameters associated with the argument being processed known ? if (numberOfParametersToProcess == 0) { currentArgDesc->addParameter("true"); } else if (numberOfParametersToProcess > 0) { string missingParameterError = "Argument %1 has %2 value(s) associated whereas exacly %3 are expected."; for (int j = 1; j <= numberOfParametersToProcess; ++j) { if (i + j >= arguments.size()) { // this->Internal->ErrorString = // missingParameterError.arg(argument).arg(j-1).arg(numberOfParametersToProcess); // if (this->Internal->Debug) { std::cout << this->Internal->ErrorString; } if (ok) { *ok = false; } return map(); } string parameter = arguments.at(i + j); if (this->Internal->Debug) { std::cout << " Processing parameter" << j << ", value:" << parameter; } if (this->argumentAdded(parameter)) { // this->Internal->ErrorString = // missingParameterError.arg(argument).arg(j-1).arg(numberOfParametersToProcess); // if (this->Internal->Debug) { std::cout << this->Internal->ErrorString; } if (ok) { *ok = false; } return map(); } if (!currentArgDesc->addParameter(parameter)) { // this->Internal->ErrorString = string( // "Value(s) associated with argument %1 are incorrect. %2"). // arg(argument).arg(currentArgDesc->ExactMatchFailedMessage); // if (this->Internal->Debug) { std::cout << this->Internal->ErrorString; } if (ok) { *ok = false; } return map(); } } // Update main loop increment i = i + numberOfParametersToProcess; } else if (numberOfParametersToProcess == -1) { if (this->Internal->Debug) { std::cout << " Proccessing StringList ..."; } int j = 1; while (j + i < arguments.size()) { if (this->argumentAdded(arguments.at(j + i))) { if (this->Internal->Debug) { std::cout << " No more parameter for" << argument; } break; } string parameter = arguments.at(j + i); if (parameter.compare(0, Internal->LongPrefix.size(), Internal->LongPrefix) == 0 || parameter.compare(0, Internal->ShortPrefix.size(), Internal->ShortPrefix) == 0) { j--; break; } if (this->Internal->Debug) { std::cout << " Processing parameter" << j << ", value:" << parameter; } if (!currentArgDesc->addParameter(parameter)) { // this->Internal->ErrorString = string( // "Value(s) associated with argument %1 are incorrect. %2"). // arg(argument).arg(currentArgDesc->ExactMatchFailedMessage); // if (this->Internal->Debug) { std::cout << this->Internal->ErrorString; } if (ok) { *ok = false; } return map(); } j++; } // Update main loop increment i = i + j; } } else { if (this->Internal->StrictMode) { this->Internal->ErrorString = "Unknown argument "; this->Internal->ErrorString += argument; error = true; break; } if (this->Internal->Debug) { std::cout << " Skipping: Unknown argument"; } this->Internal->UnparsedArguments.push_back(argument); } } if (ok) { *ok = !error; } map parsedArguments; int obligatoryArgs = 0; vector::iterator it; for (it = Internal->ArgumentDescriptionList.begin(); it != Internal->ArgumentDescriptionList.end(); ++it) { CommandLineParserArgumentDescription *desc = *it; if (!desc->Optional) obligatoryArgs++; } int parsedObligatoryArgs = 0; for (it = parsedArgDescriptions.begin(); it != parsedArgDescriptions.end(); ++it) { CommandLineParserArgumentDescription *desc = *it; string key; if (!desc->LongArg.empty()) { key = desc->LongArg; } else { key = desc->ShortArg; } if (!desc->Optional) parsedObligatoryArgs++; std::pair elem; elem.first = key; elem.second = desc->Value; parsedArguments.insert(elem); } if (obligatoryArgs > parsedObligatoryArgs) { parsedArguments.clear(); cout << helpText(); } return parsedArguments; } // ------------------------------------------------------------------------- map mitkCommandLineParser::parseArguments(int argc, char **argv, bool *ok) { std::cout << "Running Command Line Utility *" << Title << "*" << std::endl; StringContainerType arguments; // Create a StringContainerType of arguments for (int i = 0; i < argc; ++i) arguments.push_back(argv[i]); return this->parseArguments(arguments, ok); } // ------------------------------------------------------------------------- string mitkCommandLineParser::errorString() const { return this->Internal->ErrorString; } // ------------------------------------------------------------------------- const mitkCommandLineParser::StringContainerType &mitkCommandLineParser::unparsedArguments() const { return this->Internal->UnparsedArguments; } // -------------------------------------------------------------------------- void mitkCommandLineParser::addArgument(const string &longarg, const string &shortarg, Type type, const string &argLabel, const string &argHelp, const us::Any &defaultValue, bool optional, bool ignoreRest, bool deprecated, mitkCommandLineParser::Channel channel) { if (longarg.empty() && shortarg.empty()) { return; } /* Make sure it's not already added */ bool added = (this->Internal->ArgNameToArgumentDescriptionMap.count(longarg) != 0); if (added) { return; } added = (this->Internal->ArgNameToArgumentDescriptionMap.count(shortarg) != 0); if (added) { return; } auto argDesc = new CommandLineParserArgumentDescription(longarg, this->Internal->LongPrefix, shortarg, this->Internal->ShortPrefix, type, argHelp, argLabel, defaultValue, ignoreRest, deprecated, optional, channel, ParameterGroupName, ParameterGroupDescription); std::string::size_type argWidth = 0; if (!longarg.empty()) { this->Internal->ArgNameToArgumentDescriptionMap[longarg] = argDesc; argWidth += longarg.size() + this->Internal->LongPrefix.size(); } if (!shortarg.empty()) { this->Internal->ArgNameToArgumentDescriptionMap[shortarg] = argDesc; argWidth += shortarg.size() + this->Internal->ShortPrefix.size() + 2; } argWidth += 5; // Set the field width for the arguments if (argWidth > this->Internal->FieldWidth) { this->Internal->FieldWidth = argWidth; } this->Internal->ArgumentDescriptionList.push_back(argDesc); this->Internal->GroupToArgumentDescriptionListMap[this->Internal->CurrentGroup].push_back(argDesc); } // -------------------------------------------------------------------------- void mitkCommandLineParser::addDeprecatedArgument(const string &longarg, const string &shortarg, const string &argLabel, const string &argHelp) { addArgument(longarg, shortarg, StringList, argLabel, argHelp, us::Any(), false, true, false); } // -------------------------------------------------------------------------- std::vector < std::map > mitkCommandLineParser::getArgumentList() { std::vector < std::map > parameterList; //for (CommandLineParserArgumentDescription* argument : this->Internal->ArgumentDescriptionList) for (std::size_t i = 0; i< this->Internal->ArgumentDescriptionList.size(); ++i) { CommandLineParserArgumentDescription* argument = this->Internal->ArgumentDescriptionList[i]; std::map tmpMap; //tmpMap["helptext"] = us::Any(argument->helpText); tmpMap["longarg"] = us::Any(argument->LongArg); tmpMap["longargprefix"] = us::Any(argument->LongArgPrefix); tmpMap["shortarg"] = us::Any(argument->ShortArg); tmpMap["shortargprefix"] = us::Any(argument->ShortArgPrefix); tmpMap["arghelp"] = us::Any(argument->ArgHelp); tmpMap["arglabel"] = us::Any(argument->ArgLabel); tmpMap["arggroup"] = us::Any(argument->ArgGroup); tmpMap["arggroupdescription"] = us::Any(argument->ArgGroupDescription); tmpMap["ignorerest"] = us::Any(argument->IgnoreRest); tmpMap["numberofparameterstoprocess"] = us::Any(argument->NumberOfParametersToProcess); tmpMap["deprecated"] = us::Any(argument->Deprecated); tmpMap["optional"] = us::Any(argument->Optional); tmpMap["defaultvalue"] = argument->DefaultValue; tmpMap["value"] = argument->Value; tmpMap["valuetype"] = us::Any(argument->ValueType); tmpMap["channel"] = us::Any(argument->Channel); parameterList.push_back(tmpMap); } return parameterList; } // -------------------------------------------------------------------------- std::string::size_type mitkCommandLineParser::fieldWidth() const { return this->Internal->FieldWidth; } // -------------------------------------------------------------------------- void mitkCommandLineParser::beginGroup(const string &description) { this->Internal->CurrentGroup = description; } // -------------------------------------------------------------------------- void mitkCommandLineParser::endGroup() { this->Internal->CurrentGroup.clear(); } // -------------------------------------------------------------------------- string mitkCommandLineParser::helpText() const { string text; vector deprecatedArgs; text = "Command Line Utility *" + Title + "* in Category *" + Category + "*\n"; text += Description + "\n"; text += Contributor + "\n\n"; text += "Use --xml to generate an XML description parsable as a CTK Command Line Module Plugin.\n"; text += "Use --version to print MITK revision information.\n"; // Loop over grouped argument descriptions map>::iterator it; for (it = Internal->GroupToArgumentDescriptionListMap.begin(); it != Internal->GroupToArgumentDescriptionListMap.end(); ++it) { if (!(*it).first.empty()) { text = text + "\n" + (*it).first + "\n"; } vector::iterator it2; for (it2 = (*it).second.begin(); it2 != (*it).second.end(); ++it2) { CommandLineParserArgumentDescription *argDesc = *it2; if (argDesc->Deprecated) { deprecatedArgs.push_back(argDesc); } else { text += argDesc->helpText(); } } } if (!deprecatedArgs.empty()) { text += "\nDeprecated arguments:\n"; vector::iterator it2; for (it2 = deprecatedArgs.begin(); it2 != deprecatedArgs.end(); ++it2) { CommandLineParserArgumentDescription *argDesc = *it2; text += argDesc->helpText(); } } return text; } // -------------------------------------------------------------------------- bool mitkCommandLineParser::argumentAdded(const string &argument) const { return (this->Internal->ArgNameToArgumentDescriptionMap.count(argument) != 0); } // -------------------------------------------------------------------------- bool mitkCommandLineParser::argumentParsed(const string &argument) const { for (unsigned int i = 0; i < Internal->ProcessedArguments.size(); i++) if (argument.compare(Internal->ProcessedArguments.at(i)) == 0) return true; return false; } // -------------------------------------------------------------------------- void mitkCommandLineParser::setArgumentPrefix(const string &longPrefix, const string &shortPrefix) { this->Internal->LongPrefix = longPrefix; this->Internal->ShortPrefix = shortPrefix; } // -------------------------------------------------------------------------- void mitkCommandLineParser::setStrictModeEnabled(bool strictMode) { this->Internal->StrictMode = strictMode; } void mitkCommandLineParser::generateXmlOutput() { std::stringstream xml; xml << "" << endl; xml << "" << Category << "" << endl; xml << "" << Title << "" << endl; xml << "" << Description << "" << endl; xml << "" << Contributor << "" << endl; xml << "" << endl; std::vector::iterator it; std::string lastParameterGroup = ""; for (it = this->Internal->ArgumentDescriptionList.begin(); it != this->Internal->ArgumentDescriptionList.end(); it++) { std::string type; switch ((*it)->ValueType) { case mitkCommandLineParser::String: type = "string"; break; case mitkCommandLineParser::Bool: type = "boolean"; break; case mitkCommandLineParser::StringList: type = "string-vector"; break; case mitkCommandLineParser::Int: type = "integer"; break; case mitkCommandLineParser::Float: type = "float"; break; case mitkCommandLineParser::Directory: type = "directory"; break; case mitkCommandLineParser::Image: type = "image"; break; case mitkCommandLineParser::File: type = "file"; break; } if (lastParameterGroup.compare((*it)->ArgGroup)) { if (it != this->Internal->ArgumentDescriptionList.begin()) { xml << "" << endl; xml << "" << endl; } xml << "" << endl; xml << "" << (*it)->ArgGroupDescription << "" << endl; lastParameterGroup = (*it)->ArgGroup; } // Skip help item, as it's no use in GUI if ((*it)->ShortArg == "h") continue; auto name = (*it)->LongArg; if (name.empty()) name = (*it)->ShortArg; xml << "<" << type << ">" << endl; xml << "" << name << "" << endl; xml << "" << (*it)->ArgHelp << "" << endl; xml << "" << endl; if (!(*it)->DefaultValue.Empty()) xml << "" << (*it)->DefaultValue.ToString() << "" << endl; xml << "" << (*it)->LongArg << "" << endl; xml << "" << (*it)->ShortArg << "" << endl; if (((*it)->ValueType == mitkCommandLineParser::File || (*it)->ValueType == mitkCommandLineParser::Directory || (*it)->ValueType == mitkCommandLineParser::Image) && (*it)->Channel == mitkCommandLineParser::Channel::Input) { xml << "input" << endl; } else if (((*it)->ValueType == mitkCommandLineParser::File || (*it)->ValueType == mitkCommandLineParser::Directory || (*it)->ValueType == mitkCommandLineParser::Image) && (*it)->Channel == mitkCommandLineParser::Channel::Output) { xml << "output" << endl; } else if ((*it)->Channel == mitkCommandLineParser::Channel::Output || (*it)->Channel == mitkCommandLineParser::Channel::Input) { std::cout << "Only the types Directory, File or Image may be flagged as Input or Output! Ignoring flag for parameter " + name << std::endl; } xml << "" << endl; } xml << "" << endl; xml << "" << endl; cout << xml.str(); } void mitkCommandLineParser::setTitle(string title) { Title = title; } void mitkCommandLineParser::setContributor(string contributor) { Contributor = contributor; } void mitkCommandLineParser::setCategory(string category) { Category = category; } void mitkCommandLineParser::setDescription(string description) { Description = description; } void mitkCommandLineParser::changeParameterGroup(string name, string tooltip) { ParameterGroupName = name; ParameterGroupDescription = tooltip; } diff --git a/Modules/QtWidgets/include/QmitkDataStorageComboBox.h b/Modules/QtWidgets/include/QmitkDataStorageComboBox.h index 1dd4cbc7a9..e271d0215d 100644 --- a/Modules/QtWidgets/include/QmitkDataStorageComboBox.h +++ b/Modules/QtWidgets/include/QmitkDataStorageComboBox.h @@ -1,243 +1,229 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkDataStorageComboBox_h #define QmitkDataStorageComboBox_h #include // Own Includes #include "mitkDataNode.h" #include "mitkDataStorage.h" #include "mitkNodePredicateBase.h" #include "mitkWeakPointer.h" // Toolkit Includes #include #include -// Forward Declartions - /// /// \ingroup QmitkModule /// \class QmitkDataStorageComboBox /// \author Michael Mueller /// \version 4.0 /// \date 2009-02-09 /// \ingroup Widgets /// \brief Displays all or a subset (defined by a predicate) of nodes of the Data Storage. /// -/// Dont forget that this class inherits from QComboBox and you can therefore use the whole API of QComboBox. /// class MITKQTWIDGETS_EXPORT QmitkDataStorageComboBox : public QComboBox { - //#CLASS-MACROS,FRIENDS Q_OBJECT - //#CTORS/DTOR public: /// /// \brief Ctor for an empty combobox. Use setDataStorage and setPredicate afterwards. /// - QmitkDataStorageComboBox(QWidget *parent = nullptr, bool _AutoSelectNewNodes = false); + QmitkDataStorageComboBox(QWidget *parent = nullptr, bool autoSelectNewNodes = false); /// - /// \brief Ctor for constructing QmitkDataStorageComboBox with given DataStorageComboBox and given _Predicate. + /// \brief Ctor for constructing QmitkDataStorageComboBox with given DataStorageComboBox and given predicate. /// - QmitkDataStorageComboBox(mitk::DataStorage *_DataStorage, - const mitk::NodePredicateBase *_Predicate, + QmitkDataStorageComboBox(mitk::DataStorage *dataStorage, + const mitk::NodePredicateBase *predicate, QWidget *parent = nullptr, - bool _AutoSelectNewNodes = false); + bool autoSelectNewNodes = false); /// /// \brief Standard Dtor. Nothing to do here. /// ~QmitkDataStorageComboBox() override; /// - /// \brief Seaches for a given node and returns a valid index or -1 if the node was not found. + /// \brief Searches for a given node and returns a valid index or -1 if the node was not found. /// - virtual int Find(const mitk::DataNode *_DataNode) const; + virtual int Find(const mitk::DataNode *dataNode) const; - //#PUBLIC GETTER public: /// /// \brief Get the DataStorage this ComboBox listens to. /// mitk::DataStorage::Pointer GetDataStorage() const; /// - /// \brief Return the predicate (may be nullptr) that is responsible for the _DataNode selection of this ComboBox. + /// \brief Return the predicate (may be nullptr) that is responsible for the dataNode selection of this ComboBox. /// const mitk::NodePredicateBase::ConstPointer GetPredicate() const; /// - /// \brief Returns the _DataNode at Index index or 0 if the index is out of bounds. + /// \brief Returns the dataNode at Index index or 0 if the index is out of bounds. /// virtual mitk::DataNode::Pointer GetNode(int index) const; /// - /// \brief Returns the selected _DataNode or 0 if there is none. + /// \brief Returns the selected dataNode or 0 if there is none. /// virtual mitk::DataNode::Pointer GetSelectedNode() const; /// /// \brief Returns all nodes that are stored in this combobox. /// mitk::DataStorage::SetOfObjects::ConstPointer GetNodes() const; /// /// Returns the AutoSelectNewItems. /// \see SetAutoSelectNewItems /// virtual bool GetAutoSelectNewItems(); - //#PUBLIC SETTER public: /// /// \brief Set the DataStorage this ComboBox should listen to. /// - /// If DataStorage is 0 nothing will be shown. If DataStorage is re-set the combobox will be resetted. + /// If DataStorage is 0 nothing will be shown. If DataStorage is reset the combobox will be reset. void SetDataStorage(mitk::DataStorage *dataStorage); /// /// \brief Set the predicate for this ComboBox. (QmitkDataStorageComboBox is now owner of the predicate) /// - /// If predicate is nullptr all nodes will be selected. If predicate changes the whole combobox will be resetted. - void SetPredicate(const mitk::NodePredicateBase *_Predicate); + /// If predicate is nullptr all nodes will be selected. If predicate changes the whole combobox will be reset. + void SetPredicate(const mitk::NodePredicateBase *predicate); /// - /// Adds a node to the ComboBox. Gets called everytime a DataStorage Add Event was thrown. + /// Adds a node to the ComboBox. Gets called every time a DataStorage Add Event was thrown. /// - virtual void AddNode(const mitk::DataNode *_DataNode); + virtual void AddNode(const mitk::DataNode *dataNode); /// /// Removes a node from the ComboBox at a specified index (if the index exists). Gets called when a DataStorage Remove /// Event was thrown. /// virtual void RemoveNode(int index); /// /// Removes a node from the ComboBox. Gets called when a DataStorage Remove Event was thrown. /// - virtual void RemoveNode(const mitk::DataNode *_DataNode); + virtual void RemoveNode(const mitk::DataNode *dataNode); /// - /// Set a _DataNode in the ComboBox at the specified index (if the index exists). + /// Set a dataNode in the ComboBox at the specified index (if the index exists). /// Internally the method just calls RemoveNode(unsigned int) /// - virtual void SetNode(int index, const mitk::DataNode *_DataNode); + virtual void SetNode(int index, const mitk::DataNode *dataNode); /// - /// Replaces a _DataNode in the combobox by an _OtherDataNode. + /// Replaces a dataNode in the combobox by an otherDataNode. /// Internally the method just calls SetNode(unsigned int, mitk::DataNode*) /// - virtual void SetNode(const mitk::DataNode *_DataNode, const mitk::DataNode *_OtherDataNode); + virtual void SetNode(const mitk::DataNode *dataNode, const mitk::DataNode *otherDataNode); /// /// Sets AutoSelectNewItems flag. If set to true new Nodes will be automatically selected. Default is false. /// - virtual void SetAutoSelectNewItems(bool _AutoSelectNewItems); + virtual void SetAutoSelectNewItems(bool autoSelectNewItems); /// - /// \brief Called when a node is deleted or the name property of the node was modified. Calls RemoveNode or SetNode - /// then. + /// \brief Called when the name property of the node was modified. /// - virtual void OnDataNodeDeleteOrModified(const itk::Object *caller, const itk::EventObject &event); + virtual void OnPropertyListChanged(const itk::Object *caller, const itk::EventObject &event); signals: /// - /// \brief Throw a signal when the _DataNode selection changed. + /// \brief Throw a signal when the data node selection changed. /// void OnSelectionChanged(const mitk::DataNode *); - //#PROTECTED GETTER protected: /// /// \brief Checks if the given index is within the range of the m_Nodes vector. /// bool HasIndex(unsigned int index) const; - //#PROTECTED SETTER protected slots: /// /// \brief Slot for signal when the user selects another item. /// void OnCurrentIndexChanged(int); - //#PUBLIC SETTER public slots: /// /// \brief Slot for signal when user wants to set a node as current selected node. /// - void SetSelectedNode(mitk::DataNode::Pointer item); + void SetSelectedNode(const mitk::DataNode::Pointer& node); protected: /// - /// \brief Inserts a new node at the given index. If the index does not exist, the _DataNode is simply appended to the - /// combobox. + /// \brief Inserts a new node at the given index. If the index does not exist, + /// the data node is simply appended to the combobox. /// /// This function is used by AddNode() and SetNode() because they just to the same: /// 1. If node is replaced (that is when index exists), /// the itk::Event observer will be removed /// 2. Check Node against Predicate /// 3. Register for itk::Events on the node /// 4. Insert Node and show in combobox - virtual void InsertNode(int index, const mitk::DataNode *_DataNode); + virtual void InsertNode(int index, const mitk::DataNode *dataNode); /// - /// \brief Init-function this class with the given dataStorage and _Predicate. This function is called by all ctors. + /// \brief Init-function this class with the given data storage and predicate. This function is called by all ctors. /// void Init(); /// - /// \brief Reset function whenever datastorage or predicate changes. + /// \brief Reset function whenever data storage or predicate changes. /// virtual void Reset(); + void RemoveNodeAndPropertyLists(int index); + + virtual void UpdateComboBoxText(const mitk::PropertyList*); + protected: - //#PROTECTED MEMBER VARS /// /// Pointer to the DataStorage from which the nodes are selected (remember: in BlueBerry there /// might be more than one DataStorage). /// mitk::WeakPointer m_DataStorage; /// - /// \brief Holds the predicate that is responsible for the _DataNode selection of this ComboBox. - /// If the predicate is 0, every _DataNode will be selected. + /// \brief Holds the predicate that is responsible for the dataNode selection of this ComboBox. + /// If the predicate is 0, every dataNode will be selected. /// mitk::NodePredicateBase::ConstPointer m_Predicate; /// - /// Holds all selected Nodes. Dont hold smart pointer as we are in a GUI class. + /// Holds all selected Nodes. Don't hold smart pointer as we are in a GUI class. /// std::vector m_Nodes; /// - /// \brief Holds the tags of the node-modified observers. (must be updated everytime m_Nodes changes) - /// - std::vector m_NodesModifiedObserverTags; - - /// - /// \brief Holds the tags of the node-modified observers. (must be updated everytime m_Nodes changes) + /// \brief Holds the tags of the data node property observers. /// - std::vector m_NodesDeleteObserverTags; + std::vector m_DataNodePropertyListObserverTags; /// - /// \brief Maps a a specific node to (Name-)property. This is needed because we have to find the assiociated node - /// whenever the name property of a node changed. + /// \brief Holds the tags of the base data property observers. /// - std::map m_PropertyToNode; + std::vector m_BaseDatapropertyListObserverTags; /// /// \brief Event function guard. Each function which is called by an event mechanism /// first checks if this is true in order to avoid endless loops. bool m_BlockEvents; /// /// \brief If set to "true" new Nodes will be automatically selected. bool m_AutoSelectNewNodes; }; #endif // QmitkDataStorageComboBox_h diff --git a/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h b/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h index a80673b07a..2ee588a200 100644 --- a/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h +++ b/Modules/QtWidgets/include/QmitkDataStorageComboBoxWithSelectNone.h @@ -1,151 +1,151 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) University College London (UCL). All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef QmitkDataStorageComboBoxWithSelectNone_h #define QmitkDataStorageComboBoxWithSelectNone_h #include #include "QmitkDataStorageComboBox.h" #include "QmitkCustomVariants.h" #include "mitkDataNode.h" /** * \class QmitkDataStorageComboBoxWithSelectNone * \brief Displays all or a subset (defined by a predicate) of nodes of the Data Storage, * and additionally, index 0 is always "please select", indicating no selection, and will * hence always return a nullptr mitk::DataNode* if asked for the node at index 0. * * \author Matt Clarkson (m.clarkson@ucl.ac.uk) * \ingroup org_mitk_gui_qt_cmdlinemodules_internal * \sa QmitkDataStorageComboBox */ class MITKQTWIDGETS_EXPORT QmitkDataStorageComboBoxWithSelectNone : public QmitkDataStorageComboBox { Q_OBJECT Q_PROPERTY(mitkDataNodePtr SelectedNode READ GetSelectedNode WRITE SetSelectedNode) Q_PROPERTY(QString currentValue READ currentValue WRITE setCurrentValue) public: /** * \brief Calls base class constructor. * \see QmitkDataStorageComboBox */ QmitkDataStorageComboBoxWithSelectNone(QWidget* parent = nullptr, bool autoSelectNewNodes=false); /** * \brief Calls base class constructor. * \see QmitkDataStorageComboBox */ - QmitkDataStorageComboBoxWithSelectNone( mitk::DataStorage* _DataStorage, + QmitkDataStorageComboBoxWithSelectNone( mitk::DataStorage* dataStorage, const mitk::NodePredicateBase* predicate, QWidget* parent = nullptr, - bool autoSelectNewNodes=false); + bool autoSelectNewNodes = false); /** * \brief Nothing to do. * \see QmitkDataStorageComboBox */ ~QmitkDataStorageComboBoxWithSelectNone() override; /** * \brief Stores the string that will be present on index 0, currently equal to "please select". */ static const QString ZERO_ENTRY_STRING; /** * \brief Searches for a given node, returning the index if found. * \param dataNode an mitk::DataNode, can be nullptr. * \return int -1 if not found, and compared to base class, will add 1 onto the retrieved index. */ - int Find( const mitk::DataNode* dataNode ) const override; + int Find(const mitk::DataNode* dataNode) const override; /** * \brief Retrieves the node at a given index, where if index is zero, will always return nullptr. * \param index An integer between 0 and n = number of nodes. * \return mitk::DataNode::Pointer nullptr or a data node pointer. */ mitk::DataNode::Pointer GetNode(int index) const override; /** * \brief Returns the selected DataNode or nullptr if there is none, or the current index is zero. */ mitk::DataNode::Pointer GetSelectedNode() const override; /** * \brief Sets the combo box to the index that contains the specified node, or 0 if the node cannot be found. */ virtual void SetSelectedNode(const mitk::DataNode::Pointer& node); using QmitkDataStorageComboBox::RemoveNode; /** * \brief Removes a node from the ComboBox at a specified index (if the index exists). * Gets called when a DataStorage Remove Event was thrown. */ void RemoveNode(int index) override; using QmitkDataStorageComboBox::SetNode; /** * \brief Set a DataNode in the ComboBox at the specified index (if the index exists). * Internally the method just calls InsertNode(unsigned int) */ void SetNode(int index, const mitk::DataNode* dataNode) override; /** * \brief Get the current file path. */ virtual QString currentValue() const; /** * \brief Set the current file path. */ virtual void setCurrentValue(const QString& path); /** * \brief Set the string that will be present on index 0. */ void SetZeroEntryText(const QString& zeroEntryString); protected: /** * \brief Checks if the given index is within range. */ bool HasIndex(unsigned int index) const; /** * \brief Inserts a new node at the given index, unless index is 0, which is silently ignored. */ void InsertNode(int index, const mitk::DataNode* dataNode) override; /** * \brief Reset function whenever datastorage or predicate changes. */ void Reset() override; private: /** * \brief This should store the current file path of the current image. * * * The reason is so that we can store and retrieve a temporary file name. */ QString m_CurrentPath; }; #endif // QmitkDataStorageComboBoxWithSelectNone_h diff --git a/Modules/QtWidgets/src/QmitkDataStorageComboBox.cpp b/Modules/QtWidgets/src/QmitkDataStorageComboBox.cpp index 9a498c94df..9710f7b704 100644 --- a/Modules/QtWidgets/src/QmitkDataStorageComboBox.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageComboBox.cpp @@ -1,435 +1,471 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkDataStorageComboBox.h" #include -//#CTORS/DTOR - -QmitkDataStorageComboBox::QmitkDataStorageComboBox(QWidget *parent, bool _AutoSelectNewNodes) +QmitkDataStorageComboBox::QmitkDataStorageComboBox(QWidget *parent, bool autoSelectNewNodes) : QComboBox(parent), m_DataStorage(nullptr), m_Predicate(nullptr), m_BlockEvents(false), - m_AutoSelectNewNodes(_AutoSelectNewNodes) + m_AutoSelectNewNodes(autoSelectNewNodes) { this->Init(); } -QmitkDataStorageComboBox::QmitkDataStorageComboBox(mitk::DataStorage *_DataStorage, - const mitk::NodePredicateBase *_Predicate, +QmitkDataStorageComboBox::QmitkDataStorageComboBox(mitk::DataStorage *dataStorage, + const mitk::NodePredicateBase *predicate, QWidget *parent, - bool _AutoSelectNewNodes) + bool autoSelectNewNodes) : QComboBox(parent), m_DataStorage(nullptr), - m_Predicate(_Predicate), + m_Predicate(predicate), m_BlockEvents(false), - m_AutoSelectNewNodes(_AutoSelectNewNodes) + m_AutoSelectNewNodes(autoSelectNewNodes) { // make connections, fill combobox this->Init(); - this->SetDataStorage(_DataStorage); + this->SetDataStorage(dataStorage); } QmitkDataStorageComboBox::~QmitkDataStorageComboBox() { // if there was an old storage, remove listeners if (!m_DataStorage.IsExpired()) { auto dataStorage = m_DataStorage.Lock(); dataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageComboBox::AddNode)); dataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkDataStorageComboBox::RemoveNode)); } - // we have lots of observers to nodes and their name properties, this get's ugly if nodes live longer than the box + // we have lots of observers to nodes and their name properties, this gets ugly if nodes live longer than the box while (m_Nodes.size() > 0) RemoveNode(0); } -//#PUBLIC GETTER +int QmitkDataStorageComboBox::Find(const mitk::DataNode *dataNode) const +{ + std::iterator_traits::difference_type index = -1; + + auto nodeIt = std::find(m_Nodes.begin(), m_Nodes.end(), dataNode); + + if (nodeIt != m_Nodes.end()) + index = std::distance(m_Nodes.begin(), nodeIt); + + return static_cast(index); +} + mitk::DataStorage::Pointer QmitkDataStorageComboBox::GetDataStorage() const { return m_DataStorage.Lock(); } const mitk::NodePredicateBase::ConstPointer QmitkDataStorageComboBox::GetPredicate() const { return m_Predicate.GetPointer(); } mitk::DataNode::Pointer QmitkDataStorageComboBox::GetNode(int index) const { return (this->HasIndex(index)) ? m_Nodes.at(index) : nullptr; } mitk::DataNode::Pointer QmitkDataStorageComboBox::GetSelectedNode() const { if (this->count() == 0) return nullptr; int currentIndex = this->currentIndex(); return currentIndex >= 0 ? this->GetNode(currentIndex) : nullptr; } mitk::DataStorage::SetOfObjects::ConstPointer QmitkDataStorageComboBox::GetNodes() const { - mitk::DataStorage::SetOfObjects::Pointer _SetOfObjects = mitk::DataStorage::SetOfObjects::New(); + mitk::DataStorage::SetOfObjects::Pointer setOfObjects = mitk::DataStorage::SetOfObjects::New(); for (auto it = m_Nodes.begin(); it != m_Nodes.end(); ++it) { - _SetOfObjects->push_back(*it); + setOfObjects->push_back(*it); } - return _SetOfObjects.GetPointer(); + return setOfObjects.GetPointer(); } bool QmitkDataStorageComboBox::GetAutoSelectNewItems() { return m_AutoSelectNewNodes; } -//#PUBLIC SETTER -void QmitkDataStorageComboBox::SetDataStorage(mitk::DataStorage *_DataStorage) +void QmitkDataStorageComboBox::SetDataStorage(mitk::DataStorage *dataStorage) { - auto dataStorage = m_DataStorage.Lock(); + auto currentDataStorage = m_DataStorage.Lock(); // reset only if datastorage really changed - if (dataStorage.GetPointer() != _DataStorage) + if (currentDataStorage.GetPointer() != dataStorage) { // if there was an old storage, remove listeners - if (dataStorage.IsNotNull()) + if (currentDataStorage.IsNotNull()) { - dataStorage->AddNodeEvent.RemoveListener( + currentDataStorage->AddNodeEvent.RemoveListener( mitk::MessageDelegate1(this, - &QmitkDataStorageComboBox::AddNode)); + &QmitkDataStorageComboBox::AddNode)); - dataStorage->RemoveNodeEvent.RemoveListener( - mitk::MessageDelegate1( - this, &QmitkDataStorageComboBox::RemoveNode)); + currentDataStorage->RemoveNodeEvent.RemoveListener( + mitk::MessageDelegate1(this, + &QmitkDataStorageComboBox::RemoveNode)); } // set new storage - m_DataStorage = _DataStorage; + m_DataStorage = dataStorage; // if there is a new storage, add listeners if (!m_DataStorage.IsExpired()) { - dataStorage = m_DataStorage.Lock(); + currentDataStorage = m_DataStorage.Lock(); - dataStorage->AddNodeEvent.AddListener( + currentDataStorage->AddNodeEvent.AddListener( mitk::MessageDelegate1(this, - &QmitkDataStorageComboBox::AddNode)); + &QmitkDataStorageComboBox::AddNode)); - dataStorage->RemoveNodeEvent.AddListener( - mitk::MessageDelegate1( - this, &QmitkDataStorageComboBox::RemoveNode)); + currentDataStorage->RemoveNodeEvent.AddListener( + mitk::MessageDelegate1(this, + &QmitkDataStorageComboBox::RemoveNode)); } // reset predicate to reset the combobox this->Reset(); } } -void QmitkDataStorageComboBox::SetPredicate(const mitk::NodePredicateBase *_Predicate) +void QmitkDataStorageComboBox::SetPredicate(const mitk::NodePredicateBase *predicate) { - if (m_Predicate != _Predicate) + if (m_Predicate != predicate) { - m_Predicate = _Predicate; + m_Predicate = predicate; this->Reset(); } } -void QmitkDataStorageComboBox::AddNode(const mitk::DataNode *_DataNode) +void QmitkDataStorageComboBox::AddNode(const mitk::DataNode *dataNode) { - // this is an event function, make sure that we didnt call ourself + // this is an event function, make sure that we didn't call ourself if (!m_BlockEvents) { m_BlockEvents = true; - // pass a -1 to the InsertNode function in order to append the datatreenode to the end - this->InsertNode(-1, _DataNode); + // pass a -1 to the InsertNode function in order to append the datatree node to the end + this->InsertNode(-1, dataNode); m_BlockEvents = false; } } void QmitkDataStorageComboBox::RemoveNode(int index) { if (this->HasIndex(index)) { - //# remove itk::Event observer - mitk::DataNode *_DataNode = m_Nodes.at(index); - // get name property first - mitk::BaseProperty *nameProperty = _DataNode->GetProperty("name"); - // if prop exists remove modified listener - if (nameProperty) - { - nameProperty->RemoveObserver(m_NodesModifiedObserverTags[index]); - // remove name property map - m_PropertyToNode.erase(_DataNode); - } - // then remove delete listener on the node itself - _DataNode->RemoveObserver(m_NodesDeleteObserverTags[index]); - // remove observer tags from lists - m_NodesModifiedObserverTags.erase(m_NodesModifiedObserverTags.begin() + index); - m_NodesDeleteObserverTags.erase(m_NodesDeleteObserverTags.begin() + index); - // remove node from node vector - m_Nodes.erase(m_Nodes.begin() + index); + RemoveNodeAndPropertyLists(index); // remove node name from combobox this->removeItem(index); } } -void QmitkDataStorageComboBox::RemoveNode(const mitk::DataNode *_DataNode) +void QmitkDataStorageComboBox::RemoveNode(const mitk::DataNode *dataNode) { - // this is an event function, make sure that we didnt call ourself + // this is an event function, make sure that we didn't call ourself if (!m_BlockEvents) { m_BlockEvents = true; - this->RemoveNode(this->Find(_DataNode)); + this->RemoveNode(this->Find(dataNode)); m_BlockEvents = false; } } -void QmitkDataStorageComboBox::SetNode(int index, const mitk::DataNode *_DataNode) +void QmitkDataStorageComboBox::SetNode(int index, const mitk::DataNode *dataNode) { if (this->HasIndex(index)) { - this->InsertNode(index, _DataNode); + // if node is identical, we only update the name in the QComboBoxItem + if (dataNode == m_Nodes.at(index)) + { + this->setItemText(index, QString::fromStdString(dataNode->GetName())); + } + else + { + this->InsertNode(index, dataNode); + } } } -void QmitkDataStorageComboBox::SetNode(const mitk::DataNode *_DataNode, const mitk::DataNode *_OtherDataNode) +void QmitkDataStorageComboBox::SetNode(const mitk::DataNode *dataNode, const mitk::DataNode *otherDataNode) { - this->SetNode(this->Find(_DataNode), _OtherDataNode); + this->SetNode(this->Find(dataNode), otherDataNode); } -void QmitkDataStorageComboBox::SetAutoSelectNewItems(bool _AutoSelectNewItems) +void QmitkDataStorageComboBox::SetAutoSelectNewItems(bool autoSelectNewItems) { - m_AutoSelectNewNodes = _AutoSelectNewItems; + m_AutoSelectNewNodes = autoSelectNewItems; } -void QmitkDataStorageComboBox::OnDataNodeDeleteOrModified(const itk::Object *caller, const itk::EventObject &event) +void QmitkDataStorageComboBox::OnPropertyListChanged(const itk::Object *caller, const itk::EventObject &event) { if (!m_BlockEvents) { m_BlockEvents = true; - // check if we have a modified event (if not it is a delete event) + // check if we have a modified event const itk::ModifiedEvent *modifiedEvent = dynamic_cast(&event); - - // when node was modified reset text if (modifiedEvent) { - const mitk::BaseProperty *_NameProperty = dynamic_cast(caller); - - // node name changed, set it - // but first of all find associated node - for (auto it = m_PropertyToNode.begin(); it != m_PropertyToNode.end(); ++it) - { - // property is found take node - if (it->second == _NameProperty) - { - // looks strange but when calling setnode with the same node, that means the node gets updated - this->SetNode(it->first, it->first); - break; - } - } - } - else - { - const mitk::DataNode *_ConstDataNode = dynamic_cast(caller); - if (_ConstDataNode) - // node will be deleted, remove it - this->RemoveNode(_ConstDataNode); + const mitk::PropertyList *propertyList = dynamic_cast(caller); + UpdateComboBoxText(propertyList); } m_BlockEvents = false; } } -void QmitkDataStorageComboBox::SetSelectedNode(mitk::DataNode::Pointer item) -{ - int index = this->Find(item); - if (index == -1) - { - MITK_INFO << "QmitkDataStorageComboBox: item not available"; - } - else - { - this->setCurrentIndex(index); - } -} - -//#PROTECTED GETTER bool QmitkDataStorageComboBox::HasIndex(unsigned int index) const { return (m_Nodes.size() > 0 && index < m_Nodes.size()); } -int QmitkDataStorageComboBox::Find(const mitk::DataNode *_DataNode) const -{ - int index = -1; - - auto nodeIt = std::find(m_Nodes.begin(), m_Nodes.end(), _DataNode); - - if (nodeIt != m_Nodes.end()) - index = std::distance(m_Nodes.begin(), nodeIt); - - return index; -} - -//#PROTECTED SETTER void QmitkDataStorageComboBox::OnCurrentIndexChanged(int index) { if (index >= 0 && index < this->count()) emit OnSelectionChanged(this->GetSelectedNode()); if (index == -1) emit OnSelectionChanged(nullptr); } -void QmitkDataStorageComboBox::InsertNode(int index, const mitk::DataNode *_DataNode) +void QmitkDataStorageComboBox::SetSelectedNode(const mitk::DataNode::Pointer& node) +{ + int index = this->Find(node); + if (index == -1) + { + MITK_INFO << "QmitkDataStorageComboBox: item not available"; + } + else + { + this->setCurrentIndex(index); + } +} + +void QmitkDataStorageComboBox::InsertNode(int index, const mitk::DataNode *dataNode) { // check new or updated node first - if (m_Predicate.IsNotNull() && !m_Predicate->CheckNode(_DataNode)) + if (m_Predicate.IsNotNull() && !m_Predicate->CheckNode(dataNode)) return; bool addNewNode = false; bool insertNewNode = false; bool changedNode = false; // if this->HasIndex(index), then a node shall be updated if (this->HasIndex(index)) { // if we really have another node at this position then ... - if (_DataNode != m_Nodes.at(index)) + if (dataNode != m_Nodes.at(index)) { // ... remove node, then proceed as usual this->RemoveNode(index); insertNewNode = true; } else changedNode = true; } // otherwise a new node shall be added, let index point to the element after the last element else { index = m_Nodes.size(); addNewNode = true; } // const cast because we need non const nodes - mitk::DataNode *_NonConstDataNode = const_cast(_DataNode); - mitk::BaseProperty *nameProperty = _NonConstDataNode->GetProperty("name"); - + mitk::DataNode *nonConstDataNode = const_cast(dataNode); if (!changedNode) { - // break on duplicated nodes (that doesnt make sense to have duplicates in the combobox) - if (this->Find(_DataNode) != -1) + // break on duplicated nodes (that doesn't make sense to have duplicates in the combobox) + if (this->Find(dataNode) != -1) return; // add modified observer - itk::MemberCommand::Pointer modifiedCommand = + itk::MemberCommand::Pointer propertyListChangedCommand = itk::MemberCommand::New(); - modifiedCommand->SetCallbackFunction(this, &QmitkDataStorageComboBox::OnDataNodeDeleteOrModified); - // !!!! add modified observer for the name - /// property of the node because this is the only thing we are interested in !!!!! - if (nameProperty) + propertyListChangedCommand->SetCallbackFunction(this, &QmitkDataStorageComboBox::OnPropertyListChanged); + + // add observer for the data node property list + mitk::PropertyList* dataNodePropertyList = nonConstDataNode->GetPropertyList(); + if (nullptr != dataNodePropertyList) { - m_NodesModifiedObserverTags.push_back(nameProperty->AddObserver(itk::ModifiedEvent(), modifiedCommand)); - m_PropertyToNode[_NonConstDataNode] = nameProperty; + m_DataNodePropertyListObserverTags.push_back(dataNodePropertyList->AddObserver(itk::ModifiedEvent(), + propertyListChangedCommand)); } - // if there is no name node save an invalid value for the observer tag (-1) else - m_NodesModifiedObserverTags.push_back(-1); + { + // fill vector with invalid value + m_DataNodePropertyListObserverTags.push_back(-1); + } - // add delete observer - itk::MemberCommand::Pointer deleteCommand = - itk::MemberCommand::New(); - deleteCommand->SetCallbackFunction(this, &QmitkDataStorageComboBox::OnDataNodeDeleteOrModified); - m_NodesDeleteObserverTags.push_back(_NonConstDataNode->AddObserver(itk::DeleteEvent(), modifiedCommand)); + mitk::PropertyList* baseDataPropertyList; + //add observer for the base data property list + mitk::BaseData* baseData = dynamic_cast(nonConstDataNode->GetData()); + if (nullptr != baseData) + { + baseDataPropertyList = baseData->GetPropertyList(); + if (nullptr != baseDataPropertyList) + { + m_BaseDatapropertyListObserverTags.push_back(baseDataPropertyList->AddObserver(itk::ModifiedEvent(), + propertyListChangedCommand)); + } + else + { + // fill vector with invalid value + m_BaseDatapropertyListObserverTags.push_back(-1); + } + } + else + { + // fill vector with invalid value + m_BaseDatapropertyListObserverTags.push_back(-1); + } } // add node to the vector if (addNewNode) - m_Nodes.push_back(_NonConstDataNode); + m_Nodes.push_back(nonConstDataNode); else if (insertNewNode) - m_Nodes.insert(m_Nodes.begin() + index, _NonConstDataNode); - - // ... and to the combobox - std::string _NonConstDataNodeName = "unnamed node"; - // _NonConstDataNodeName is "unnamed node" so far, change it if there is a name property in the node - if (nameProperty) - _NonConstDataNodeName = nameProperty->GetValueAsString(); + m_Nodes.insert(m_Nodes.begin() + index, nonConstDataNode); if (addNewNode) { - this->addItem(QString::fromStdString(_NonConstDataNodeName)); + this->addItem(QString::fromStdString(nonConstDataNode->GetName())); // select new node if m_AutoSelectNewNodes is true or if we have just added the first node if (m_AutoSelectNewNodes || m_Nodes.size() == 1) this->setCurrentIndex(index); } else { // update text in combobox - this->setItemText(index, QString::fromStdString(_NonConstDataNodeName)); + this->setItemText(index, QString::fromStdString(nonConstDataNode->GetName())); } } void QmitkDataStorageComboBox::Init() { connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(OnCurrentIndexChanged(int))); } void QmitkDataStorageComboBox::Reset() { // remove all nodes first while (!m_Nodes.empty()) { // remove last node - // explicietely calling RemoveNode of QmitkDataStorageComboBox since derived classes may prevent the removal of all + // explicitly calling RemoveNode of QmitkDataStorageComboBox since derived classes may prevent the removal of all // nodes in their respective RemoveNode implementation. This is happening for example in // QmitkDataStorageComboBoxWithSelectNone. QmitkDataStorageComboBox::RemoveNode(m_Nodes.size() - 1); } // clear combobox this->clear(); if (!m_DataStorage.IsExpired()) { auto dataStorage = m_DataStorage.Lock(); mitk::DataStorage::SetOfObjects::ConstPointer setOfObjects; // select all if predicate == nullptr if (m_Predicate.IsNotNull()) setOfObjects = dataStorage->GetSubset(m_Predicate); else setOfObjects = dataStorage->GetAll(); // add all found nodes for (mitk::DataStorage::SetOfObjects::ConstIterator nodeIt = setOfObjects->Begin(); nodeIt != setOfObjects->End(); - ++nodeIt) // for each _DataNode + ++nodeIt) // for each dataNode { // add node to the node vector and to the combobox this->AddNode(nodeIt.Value().GetPointer()); } } } + +void QmitkDataStorageComboBox::RemoveNodeAndPropertyLists(int index) +{ + // remove itk::Event observer + mitk::DataNode *dataNode = m_Nodes.at(index); + + // remove observer from data node property list + mitk::PropertyList* dataNodePropertyList = dataNode->GetPropertyList(); + if (nullptr != dataNodePropertyList) + { + dataNodePropertyList->RemoveObserver(m_DataNodePropertyListObserverTags[index]); + // remove observer tags from lists + m_DataNodePropertyListObserverTags.erase(m_DataNodePropertyListObserverTags.begin() + index); + } + + // remove observer from base data property list + mitk::BaseData* baseData = dynamic_cast(dataNode->GetData()); + if (nullptr != baseData) + { + mitk::PropertyList* baseDataPropertyList = baseData->GetPropertyList(); + if (nullptr != dataNodePropertyList) + { + baseDataPropertyList->RemoveObserver(m_BaseDatapropertyListObserverTags[index]); + // remove observer tags from lists + m_BaseDatapropertyListObserverTags.erase(m_BaseDatapropertyListObserverTags.begin() + index); + } + } + + // remove node from node vector + m_Nodes.erase(m_Nodes.begin() + index); +} + +void QmitkDataStorageComboBox::UpdateComboBoxText(const mitk::PropertyList* propertyList) +{ + mitk::PropertyList* dataNodePropertyList = nullptr; + mitk::PropertyList* baseDataPropertyList = nullptr; + mitk::BaseData* baseData; + for (const auto& node : m_Nodes) + { + dataNodePropertyList = node->GetPropertyList(); + + baseData = dynamic_cast(node->GetData()); + if (nullptr != baseData) + { + baseDataPropertyList = baseData->GetPropertyList(); + } + + if (propertyList == dataNodePropertyList + || propertyList == baseDataPropertyList) + { + // if one of the property list is the one that has just been modified + // get the node's index and set its text to the node name + // the node name might have been changed, depending on the modified property list + auto index = Find(node); + // update text in combobox + this->setItemText(index, QString::fromStdString(node->GetName())); + return; + } + } +} diff --git a/Modules/QtWidgets/src/QmitkDataStorageComboBoxWithSelectNone.cpp b/Modules/QtWidgets/src/QmitkDataStorageComboBoxWithSelectNone.cpp index e1a99a0007..488d7ac3ab 100644 --- a/Modules/QtWidgets/src/QmitkDataStorageComboBoxWithSelectNone.cpp +++ b/Modules/QtWidgets/src/QmitkDataStorageComboBoxWithSelectNone.cpp @@ -1,206 +1,139 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) University College London (UCL). All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkDataStorageComboBoxWithSelectNone.h" #include const QString QmitkDataStorageComboBoxWithSelectNone::ZERO_ENTRY_STRING = "--"; -//----------------------------------------------------------------------------- -QmitkDataStorageComboBoxWithSelectNone::QmitkDataStorageComboBoxWithSelectNone( - QWidget* parent, - bool autoSelectNewNodes ) -: QmitkDataStorageComboBox(parent, autoSelectNewNodes) -, m_CurrentPath("") +QmitkDataStorageComboBoxWithSelectNone::QmitkDataStorageComboBoxWithSelectNone(QWidget* parent, bool autoSelectNewNodes) + : QmitkDataStorageComboBox(parent, autoSelectNewNodes) + , m_CurrentPath("") { } - -//----------------------------------------------------------------------------- -QmitkDataStorageComboBoxWithSelectNone::QmitkDataStorageComboBoxWithSelectNone( - mitk::DataStorage* dataStorage, - const mitk::NodePredicateBase* predicate, - QWidget* parent, bool autoSelectNewNodes ) -: QmitkDataStorageComboBox(dataStorage, predicate, parent, autoSelectNewNodes) +QmitkDataStorageComboBoxWithSelectNone::QmitkDataStorageComboBoxWithSelectNone(mitk::DataStorage* dataStorage, + const mitk::NodePredicateBase* predicate, + QWidget* parent, + bool autoSelectNewNodes) + : QmitkDataStorageComboBox(dataStorage, predicate, parent, autoSelectNewNodes) { } - -//----------------------------------------------------------------------------- QmitkDataStorageComboBoxWithSelectNone::~QmitkDataStorageComboBoxWithSelectNone() { } - -//----------------------------------------------------------------------------- -int QmitkDataStorageComboBoxWithSelectNone::Find( const mitk::DataNode* dataNode ) const +int QmitkDataStorageComboBoxWithSelectNone::Find(const mitk::DataNode* dataNode) const { int index = QmitkDataStorageComboBox::Find(dataNode); if (index != -1) { index += 1; } return index; } - -//----------------------------------------------------------------------------- -mitk::DataNode::Pointer QmitkDataStorageComboBoxWithSelectNone::GetNode( int index ) const +mitk::DataNode::Pointer QmitkDataStorageComboBoxWithSelectNone::GetNode(int index) const { mitk::DataNode::Pointer result = nullptr; if (this->HasIndex(index)) { if (index != 0) { result = m_Nodes.at(index - 1); } } return result; } - -//----------------------------------------------------------------------------- mitk::DataNode::Pointer QmitkDataStorageComboBoxWithSelectNone::GetSelectedNode() const { return this->GetNode(this->currentIndex()); } - -//----------------------------------------------------------------------------- void QmitkDataStorageComboBoxWithSelectNone::SetSelectedNode(const mitk::DataNode::Pointer& node) { - int currentIndex = -1; - for (int i = 0; i < static_cast(m_Nodes.size()); i++) - { - if (m_Nodes[i] == node.GetPointer()) - { - currentIndex = i; - break; - } - } - if (currentIndex == -1) + int index = Find(node); + if (index == -1) { // didn't find it, so set the value to 0. - currentIndex = 0; - } - else - { - currentIndex += 1; // because the combo box contains "please select" at position zero. + index = 0; } - this->setCurrentIndex(currentIndex); + + this->setCurrentIndex(index); } -//----------------------------------------------------------------------------- -void QmitkDataStorageComboBoxWithSelectNone::RemoveNode( int index ) +void QmitkDataStorageComboBoxWithSelectNone::RemoveNode(int index) { if(index > 0 && this->HasIndex(index)) { - - // remove itk::Event observer - mitk::DataNode* dataNode = m_Nodes.at(index - 1); - - // get name property first - mitk::BaseProperty* nameProperty = dataNode->GetProperty("name"); - - // if prop exists remove modified listener - if(nameProperty) - { - nameProperty->RemoveObserver(m_NodesModifiedObserverTags[index-1]); - - // remove name property map - m_PropertyToNode.erase(dataNode); - } - - // then remove delete listener on the node itself - dataNode->RemoveObserver(m_NodesDeleteObserverTags[index-1]); - - // remove observer tags from lists - m_NodesModifiedObserverTags.erase(m_NodesModifiedObserverTags.begin()+index-1); - m_NodesDeleteObserverTags.erase(m_NodesDeleteObserverTags.begin()+index-1); - + RemoveNodeAndPropertyLists(index - 1); // remove node name from combobox this->removeItem(index); - - // remove node from node vector - m_Nodes.erase(m_Nodes.begin()+index-1); } } - -//----------------------------------------------------------------------------- void QmitkDataStorageComboBoxWithSelectNone::SetNode(int index, const mitk::DataNode* dataNode) { - if(index > 0 && this->HasIndex(index)) + if (index > 0 && this->HasIndex(index)) { - // if node identical, we only update the name in the QComboBoxItem - if( dataNode == this->m_Nodes.at(index-1 ) ) + // if node is identical, we only update the name in the QComboBoxItem + if (dataNode == m_Nodes.at(index - 1)) { - mitk::BaseProperty* nameProperty = dataNode->GetProperty("name"); - std::string dataNodeNameStr = nameProperty->GetValueAsString(); - - this->setItemText(index, QString::fromStdString( dataNodeNameStr) ); + this->setItemText(index, QString::fromStdString(dataNode->GetName())); } else + { QmitkDataStorageComboBox::InsertNode(index - 1, dataNode); + } } } - -//----------------------------------------------------------------------------- bool QmitkDataStorageComboBoxWithSelectNone::HasIndex(unsigned int index) const { return (m_Nodes.size() > 0 && index <= m_Nodes.size()); } - -//----------------------------------------------------------------------------- void QmitkDataStorageComboBoxWithSelectNone::InsertNode(int index, const mitk::DataNode* dataNode) { if (index != 0) { QmitkDataStorageComboBox::InsertNode(index - 1, dataNode); } } - -//----------------------------------------------------------------------------- void QmitkDataStorageComboBoxWithSelectNone::Reset() { QmitkDataStorageComboBox::Reset(); this->insertItem(0, ZERO_ENTRY_STRING); } - -//----------------------------------------------------------------------------- void QmitkDataStorageComboBoxWithSelectNone::SetZeroEntryText(const QString& zeroEntryString) { this->setItemText(0, zeroEntryString); this->setCurrentIndex(0); } - -//----------------------------------------------------------------------------- QString QmitkDataStorageComboBoxWithSelectNone::currentValue() const { return m_CurrentPath; } - -//----------------------------------------------------------------------------- void QmitkDataStorageComboBoxWithSelectNone::setCurrentValue(const QString& path) { m_CurrentPath = path; } diff --git a/Modules/REST/documentation/REST.dox b/Modules/REST/documentation/REST.dox index 34eab399f7..1d467e93c6 100644 --- a/Modules/REST/documentation/REST.dox +++ b/Modules/REST/documentation/REST.dox @@ -1,194 +1,194 @@ /** \page RESTModule The MITK REST Module \tableofcontents \section REST_brief Description The MITK REST Module is able to manage REST requests. The main class is the RESTManager. It is a MicroServices which can be accessed via \code{.cpp} auto *context = us::GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto managerService = context->GetService(managerRef); if (managerService) { //call the function you need from the service } } \endcode \subsection REST_Technical Technical background The module uses the Microsoft C++ REST SDK for REST mechanisms as well as JSON convertion and asynchronic programming. \section Use_REST How to use the REST Module You can use the REST module from two different perspectives in MITK:
  1. The Server view (receive requests from clients)
  2. The Client view (send requests to servers)
The following sections will give you an introduction on how to use which of those roles: \subsection Server_Use Use from a Server perspective To act as a server, you need to implement the IRESTObserver, which has a Notify() method that has to be implemented. In this Notify() method you specify how you want to react to incoming requests and with which data you want to respond to the requests. You can then start listening for requests from clients as shown below: \code{.cpp} auto *context = us::GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto managerService = context->GetService(managerRef); if (managerService) { managerService->ReceiveRequests(uri /*specify your uri which you want to receive requests for*/, this); } } \endcode If a client sends a request, the Notify method is called and a response is sent. By now, only GET-requests from clients are supported. If you want to stop listening for requests you can do this by calling \code{.cpp} auto *context = us::GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto managerService = context->GetService(managerRef); if (managerService) { managerService->HandleDeleteObserver(this, uri); } } \endcode You don't have to specify a uri in the HandleDeleteObserver method, if you only call managerService->HandleDeleteObserver(this);, all uris you receive requests for are deleted and you aren't listening to any requests anymore. \subsection Client_Use Use from a Client perspective The following example shows how to send requests from a client perspective: \code{.cpp} //Get the microservice auto *context = us::ModuleRegistry::GetModule(1)->GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto managerService = context->GetService(managerRef); if (managerService) { //Call the send request method which starts the actual request managerService - ->SendRequest(_XPLATSTR("https://jsonplaceholder.typicode.com/posts/1")) + ->SendRequest(U("https://jsonplaceholder.typicode.com/posts/1")) .then([=](pplx::task resultTask)/*It is important to use task-based continuation*/ { try { //Get the result of the request //This will throw an exception if the ascendent task threw an exception (e.g. invalid URI) web::json::value result = resultTask.get(); //Do something with the result (e.g. convert it to a QString to update an UI element) utility::string_t stringT = result.to_string(); std::string stringStd(stringT.begin(), stringT.end()); QString stringQ = QString::fromStdString(stringStd); //Note: if you want to update your UI, do this by using signals and slots. //The UI can't be updated from a Thread different to the Qt main thread emit UpdateLabel(stringQ); } catch (const mitk::Exception &exception) { //Exceptions from ascendent tasks are catched here MITK_ERROR << exception.what(); return; } }); } } \endcode The steps you need to make are the following:
  1. Get the microservice. You can get the microservice via the module context. If you want to use the microservice within a plug-in, you need to get the module context from the us::ModuleRegistry.
  2. Call the SendRequest method. This will start the request itself and is performed asynchronously. As soon as the response is sent by the server, the .then(...) block is executed.
  3. Choose parameters for .then(...) block. For exception handling, it is important to choose pplx::task . This is a task-based continuation. For more information, visit https://docs.microsoft.com/en-us/cpp/parallel/concrt/exception-handling-in-the-concurrency-runtime?view=vs-2017.
  4. Get the result of the request. You can get the JSON-value of the result by callint .get(). At this point, an exception is thrown if something in the previous tasks threw an exception.
  5. Do something with the result. \note If you want to modify GUI elements within the .then(...) block, you need to do this by using signals and slots because GUI elements can only be modified by th Qt Main Thread. For more information, visit https://doc.qt.io/Qt-5/thread-basics.html#gui-thread-and-worker-thread
  6. Exception handling. Here you can define the behaviour if an exception is thrown, exceptions from ascendent tasks are also catched here.
Code, which is followed by this codeblock shown above will be performed asynchronously while waiting for the result. Besides Get-Requests, you can also perform Put or Post requests by specifying a RequestType in the SendRequest method. The following example shows, how you can perform multiple tasks, encapsulated to one joined task. The steps are based on the example for one request and only the specific steps for encapsulation are described. \code{.cpp} //Get the microservice //Get microservice auto *context = us::ModuleRegistry::GetModule(1)->GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto managerService = context->GetService(managerRef); if (managerService) { //Create multiple tasks e.g. as shown below std::vector> tasks; for (int i = 0; i < 20; i++) { pplx::task singleTask = managerService->SendRequest(L"https://jsonplaceholder.typicode.com/posts/1") .then([=](pplx::task resultTask) { //Do something when a single task is done try { resultTask.get(); emit UpdateProgressBar(); } catch (const mitk::Exception &exception) { MITK_ERROR << exception.what(); return; } }); tasks.emplace_back(singleTask); } //Create a joinTask which includes all tasks you've created auto joinTask = pplx::when_all(begin(tasks), end(tasks)); //Run asynchonously joinTask.then([=](pplx::task resultTask) { //Do something when all tasks are finished try { resultTask.get(); emit UpdateLabel("All tasks finished"); } catch (const mitk::Exception &exception) { MITK_ERROR << exception.what(); return; } }); } \endcode The steps you need to make are the following:
  1. Get the microservice. See example above.
  2. Create multiple tasks. In this example, 20 identical tasks are created and are saved into a vector. In general, it is possible to place any tasks in that vector.
  3. Do something when a single task is done. Here, an action is performed if a single tasks is finished. In this example, a progress bar is loaded by a specific number of percent.
  4. Create a joinTask. Here, all small tasks are encapsulated in one big task.
  5. Run joinTask asynchonously. The then(...) of the joinTask is performed when all single tasks are finished.
  6. Do something when all tasks are finished. The handling of the end of a joinTask is equivalent to the end of a single tasks.
*/ diff --git a/Modules/REST/include/mitkIRESTManager.h b/Modules/REST/include/mitkIRESTManager.h index c2f71d4ef3..3da1876ec2 100644 --- a/Modules/REST/include/mitkIRESTManager.h +++ b/Modules/REST/include/mitkIRESTManager.h @@ -1,99 +1,137 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkIRESTManager_h #define mitkIRESTManager_h #include #include +#include #include #include +#include namespace mitk { class IRESTObserver; class RESTServer; /** * @class IRESTManager * @brief This is a microservice interface for managing REST requests. */ class MITKREST_EXPORT IRESTManager { public: virtual ~IRESTManager(); /** * @brief request type for client requests by calling SendRequest */ enum class RequestType { Get, Post, Put }; /** * @brief Executes a HTTP request in the mitkRESTClient class * + * @throw mitk::Exception if RequestType is not suported * @param uri defines the URI the request is send to * @param type the RequestType of the HTTP request (optional) - * @param body the body for the request (optional) + * @param headers the headers for the request (optional) * @return task to wait for */ virtual pplx::task SendRequest( + const web::uri &uri, + const RequestType &type = RequestType::Get, + const std::map headers = {}) = 0; + + /** + * @brief Executes a HTTP request in the mitkRESTClient class + * + * @param uri defines the URI the request is send to + * @param type the RequestType of the HTTP request (optional) + * @param body the body for the request (optional) + * @param headers the headers for the request (optional) + * @param filePath the file path to store the request to (optional) + * @return task to wait for + */ + virtual pplx::task SendJSONRequest( const web::uri &uri, const RequestType &type = RequestType::Get, const web::json::value *body = nullptr, + const std::map headers = {}, const utility::string_t &filePath = {} ) = 0; + /** + * @brief Executes a HTTP request in the mitkRESTClient class + * + * @param uri defines the URI the request is send to + * @param type the RequestType of the HTTP request (optional) + * @param body the body for the request (optional) + * @param headers the headers for the request (optional) + * @return task to wait for + */ + virtual pplx::task SendBinaryRequest(const web::uri &uri, + const RequestType &type = RequestType::Get, + const std::vector *body = {}, + const std::map headers = {}) = 0; + /** * @brief starts listening for requests if there isn't another observer listening and the port is free * * @param uri defines the URI for which incoming requests should be send to the observer * @param observer the observer which handles the incoming requests */ virtual void ReceiveRequest(const web::uri &uri, IRESTObserver *observer) = 0; /** * @brief Handles incoming requests by notifying the observer which should receive it * * @param uri defines the URI of the request * @param body the body of the request - * @return the data which is modified by the notified observer + * @param method the http method of the request + * @param headers the http headers of the request + * @return the response */ - virtual web::json::value Handle(const web::uri &uri, const web::json::value &body) = 0; + virtual web::http::http_response Handle(const web::uri &uri, + const web::json::value &body, + const web::http::method &method, + const mitk::RESTUtil::ParamMap &headers) = 0; /** * @brief Handles the deletion of an observer for all or a specific uri * * @param observer the observer which shouldn't receive requests anymore * @param uri the uri for which the observer doesn't handle requests anymore (optional) */ virtual void HandleDeleteObserver(IRESTObserver *observer, const web::uri &uri = {}) = 0; virtual const std::map& GetServerMap() = 0; virtual const std::map, IRESTObserver *>& GetObservers() = 0; }; } MITK_DECLARE_SERVICE_INTERFACE(mitk::IRESTManager, "org.mitk.IRESTManager") #endif diff --git a/Modules/REST/include/mitkIRESTObserver.h b/Modules/REST/include/mitkIRESTObserver.h index 9f78bbdf2b..a90e246bda 100644 --- a/Modules/REST/include/mitkIRESTObserver.h +++ b/Modules/REST/include/mitkIRESTObserver.h @@ -1,50 +1,55 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkIRESTObserver_h #define mitkIRESTObserver_h #include - +#include #include #include +#include namespace mitk { class MITKREST_EXPORT IRESTObserver { public: /** * @brief Deletes an observer and calls HandleDeleteObserver() in RESTManager class * * @see HandleDeleteObserver() */ virtual ~IRESTObserver(); /** * @brief Called if there's an incoming request for the observer, observer implements how to handle request * * @param data the data of the incoming request + * @param method the http method of the incoming request * @return the modified data */ - virtual web::json::value Notify(const web::uri &uri, const web::json::value &data) = 0; + virtual web::http::http_response Notify(const web::uri &uri, + const web::json::value &data, + const web::http::method &method, + const mitk::RESTUtil::ParamMap &headers) = 0; private: }; } #endif diff --git a/Modules/REST/include/mitkRESTClient.h b/Modules/REST/include/mitkRESTClient.h index 0f52864d49..fc3c2dac42 100644 --- a/Modules/REST/include/mitkRESTClient.h +++ b/Modules/REST/include/mitkRESTClient.h @@ -1,74 +1,106 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkRESTClient_h #define mitkRESTClient_h #include #include namespace mitk { class MITKREST_EXPORT RESTClient { public: + using http_request = web::http::http_request; RESTClient(); ~RESTClient(); /** * @brief Executes a HTTP GET request with the given uri and returns a task waiting for a json object * * @throw mitk::Exception if request went wrong * @param uri the URI resulting the target of the HTTP request + * @param the additional headers to be set to the HTTP request * @return task to wait for with resulting json object */ - pplx::task Get(const web::uri &uri); + pplx::task Get(const web::uri &uri, const std::map headers); /** * @brief Executes a HTTP GET request with the given uri and and stores the byte stream in a file given by the * filePath * * @throw mitk::Exception if request went wrong * @param uri the URI resulting the target of the HTTP request + * @param the additional headers to be set to the HTTP request * @return task to wait for returning an empty json object */ - pplx::task Get(const web::uri &uri, const utility::string_t &filePath); + pplx::task Get(const web::uri &uri, + const utility::string_t &filePath, + const std::map headers); /** * @brief Executes a HTTP PUT request with given uri and the content given as json * * @throw mitk::Exception if request went wrong * @param uri defines the URI resulting the target of the HTTP request * @param content the content as json value which should be the body of the request and thus the content of the * created resources * @return task to wait for with resulting json object */ pplx::task Put(const web::uri &uri, const web::json::value *content); /** * @brief Executes a HTTP POST request with given uri and the content given as json * * @throw mitk::Exception if request went wrong * @param uri defines the URI resulting the target of the HTTP request * @param content the content as json value which should be the body of the request and thus the content of the * created resource + * @param headers the additional headers to be set to the HTTP request * @return task to wait for with resulting json object */ - pplx::task Post(const web::uri &uri, const web::json::value *content); + pplx::task Post(const web::uri &uri, + const web::json::value *content, + const std::map headers); + + /** + * @brief Executes a HTTP POST request with given uri and the content given as json + * + * @throw mitk::Exception if request went wrong + * @param uri defines the URI resulting the target of the HTTP request + * @param content the content as json value which should be the body of the request and thus the content of the + * created resource + * @param headers the additional headers to be set to the HTTP request + * @return task to wait for with resulting json object + */ + pplx::task Post(const web::uri &uri, + const std::vector *content, + const std::map headers); + + private: + /** + * @brief Use this to create and init a new request with the given headers. If needed, set the body on the resulting + * request object to avoid an automatic change of the content type header when setting the body first. + */ + http_request InitRequest(const std::map headers); + + pplx::task ExecutePost(const web::uri &uri, http_request request); + web::http::client::http_client_config m_ClientConfig; }; -} +} // namespace mitk #endif diff --git a/Modules/REST/include/mitkRESTUtil.h b/Modules/REST/include/mitkRESTUtil.h index 4abc640f96..fdc16c0a19 100644 --- a/Modules/REST/include/mitkRESTUtil.h +++ b/Modules/REST/include/mitkRESTUtil.h @@ -1,46 +1,51 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkRESTUtil_h #define mitkRESTUtil_h #include #include +#include + namespace mitk { class MITKREST_EXPORT RESTUtil { public: + + typedef std::map ParamMap; + /** * @brief Converts the given std::wstring into a std::string representation */ static std::string convertToUtf8(const utility::string_t &string) { return utility::conversions::to_utf8string(string); } /** * @brief Converts the given std::string into a std::wstring representation */ static utility::string_t convertToTString(const std::string &string) { return utility::conversions::to_string_t(string); } }; } #endif diff --git a/Modules/REST/src/mitkRESTClient.cpp b/Modules/REST/src/mitkRESTClient.cpp index e6e6f2e755..8b4fa9dd9c 100644 --- a/Modules/REST/src/mitkRESTClient.cpp +++ b/Modules/REST/src/mitkRESTClient.cpp @@ -1,162 +1,214 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ +#include #include #include -#include -#include #include +#include using http_client = web::http::client::http_client; using http_request = web::http::http_request; using http_response = web::http::http_response; using methods = web::http::methods; using status_codes = web::http::status_codes; using file_buffer = concurrency::streams::file_buffer; using streambuf = concurrency::streams::streambuf; mitk::RESTClient::RESTClient() { + m_ClientConfig.set_validate_certificates(false); } -mitk::RESTClient::~RESTClient() -{ -} +mitk::RESTClient::~RESTClient() {} -pplx::task mitk::RESTClient::Get(const web::uri &uri) +pplx::task mitk::RESTClient::Get(const web::uri &uri, + const std::map headers) { - auto client = new http_client(uri); + auto client = new http_client(uri, m_ClientConfig); http_request request; + for (auto param : headers) + { + request.headers().add(param.first, param.second); + } + return client->request(request).then([=](pplx::task responseTask) { try { auto response = responseTask.get(); auto status = response.status_code(); if (status_codes::OK != status) + { + MITK_INFO << status; + MITK_INFO << mitk::RESTUtil::convertToUtf8(response.to_string()); mitkThrow(); - + } + auto requestContentType = response.headers().content_type(); - if (_XPLATSTR("application/json") != requestContentType) - response.headers().set_content_type(_XPLATSTR("application/json")); + if (U("application/json") != requestContentType) + response.headers().set_content_type(U("application/json")); return response.extract_json().get(); } - catch (...) + catch (std::exception &e) { + MITK_INFO << e.what(); mitkThrow() << "Getting response went wrong"; } }); } -pplx::task mitk::RESTClient::Get(const web::uri &uri, const utility::string_t &filePath) +pplx::task mitk::RESTClient::Get(const web::uri &uri, + const utility::string_t &filePath, + const std::map headers) { - auto client = new http_client(uri); + auto client = new http_client(uri, m_ClientConfig); auto fileBuffer = std::make_shared>(); http_request request; + for (auto param : headers) + { + request.headers().add(param.first, param.second); + } + // Open file stream for the specified file path return file_buffer::open(filePath, std::ios::out) .then([=](streambuf outFile) -> pplx::task { *fileBuffer = outFile; return client->request(methods::GET); }) // Write the response body into the file buffer .then([=](http_response response) -> pplx::task { auto status = response.status_code(); if (status_codes::OK != status) mitkThrow() << "GET ended up with response " << RESTUtil::convertToUtf8(response.to_string()); - + return response.body().read_to_end(*fileBuffer); }) // Close the file buffer - .then([=](size_t) { - return fileBuffer->close(); - }) + .then([=](size_t) { return fileBuffer->close(); }) // Return empty JSON object - .then([=]() { - return web::json::value(); - }); + .then([=]() { return web::json::value(); }); } pplx::task mitk::RESTClient::Put(const web::uri &uri, const web::json::value *content) { - auto client = new http_client(uri); + auto client = new http_client(uri, m_ClientConfig); http_request request(methods::PUT); if (nullptr != content) request.set_body(*content); - + return client->request(request).then([=](pplx::task responseTask) { try { auto response = responseTask.get(); auto status = response.status_code(); if (status_codes::OK != status) mitkThrow(); // Parse content type to application/json if it isn't already. This is // important if the content type is e.g. application/dicom+json. auto requestContentType = response.headers().content_type(); - if (_XPLATSTR("application/json") != requestContentType) - response.headers().set_content_type(_XPLATSTR("application/json")); + if (U("application/json") != requestContentType) + response.headers().set_content_type(U("application/json")); return response.extract_json().get(); } - catch (...) + catch (std::exception &e) { + MITK_INFO << e.what(); mitkThrow() << "Getting response went wrong"; } }); } -pplx::task mitk::RESTClient::Post(const web::uri &uri, const web::json::value *content) +pplx::task mitk::RESTClient::Post(const web::uri &uri, + const std::vector *content, + const std::map headers) +{ + auto request = InitRequest(headers); + request.set_method(methods::POST); + + if (nullptr != content) + request.set_body(*content); + + return ExecutePost(uri, request); +} + +pplx::task mitk::RESTClient::Post(const web::uri &uri, + const web::json::value *content, + const std::map headers) { - auto client = new http_client(uri); - http_request request(methods::POST); + auto request = InitRequest(headers); + request.set_method(methods::POST); if (nullptr != content) request.set_body(*content); + return ExecutePost(uri, request); +} + +http_request mitk::RESTClient::InitRequest(const std::map headers) +{ + http_request request; + + for (auto param : headers) + { + request.headers().add(param.first, param.second); + } + return request; +} + +pplx::task mitk::RESTClient::ExecutePost(const web::uri &uri, http_request request) +{ + auto client = new http_client(uri, m_ClientConfig); return client->request(request).then([=](pplx::task responseTask) { try { auto response = responseTask.get(); auto status = response.status_code(); + auto requestContentType = response.headers().content_type(); + MITK_INFO << status; - if (status_codes::Created != status) + if (status_codes::OK != status) mitkThrow(); - // Parse content type to application/json if it isn't already. This is - // important if the content type is e.g. application/dicom+json. - auto requestContentType = response.headers().content_type(); - if (_XPLATSTR("application/json") != requestContentType) - response.headers().set_content_type(_XPLATSTR("application/json")); + MITK_INFO << mitk::RESTUtil::convertToUtf8(requestContentType); + if (U("application/json") != requestContentType) + { + auto json = web::json::value::object(); + json[U("test")] = web::json::value(17); + response.set_body(json); // use random json object in response body to perform return value (Linux Fix) + response.headers().set_content_type(U("application/json")); + } return response.extract_json().get(); } - catch(...) + catch (std::exception &e) { + MITK_INFO << e.what(); mitkThrow() << "Getting response went wrong"; } }); } diff --git a/Modules/REST/src/mitkRESTServer.cpp b/Modules/REST/src/mitkRESTServer.cpp index 5902d57a0f..13333f85d4 100644 --- a/Modules/REST/src/mitkRESTServer.cpp +++ b/Modules/REST/src/mitkRESTServer.cpp @@ -1,109 +1,113 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ -#include - #include +#include #include #include #include using namespace std::placeholders; using http_listener = web::http::experimental::listener::http_listener; using http_request = web::http::http_request; +using http_response = web::http::http_response; using methods = web::http::methods; using status_codes = web::http::status_codes; namespace mitk { class RESTServer::Impl { public: Impl(const web::uri &uri); ~Impl(); - void HandleGet(const http_request &request); + void HandleRequest(const http_request &request); web::http::experimental::listener::http_listener listener; web::uri uri; }; - RESTServer::Impl::Impl(const web::uri &uri) - : uri{uri} - { - } + RESTServer::Impl::Impl(const web::uri &uri) : uri{uri} {} - RESTServer::Impl::~Impl() - { - } + RESTServer::Impl::~Impl() {} - void RESTServer::Impl::HandleGet(const http_request &request) + void RESTServer::Impl::HandleRequest(const http_request &request) { web::uri_builder builder(this->listener.uri()); builder.append(request.absolute_uri()); auto uriString = builder.to_uri().to_string(); - web::json::value content; + http_response response(status_codes::InternalError); + response.set_body(U("There went something wrong after receiving the request.")); auto context = us::GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto manager = context->GetService(managerRef); if (manager) { - auto data = request.extract_json().get(); - content = manager->Handle(builder.to_uri(), data); + // not every request contains JSON data + web::json::value data = {}; + if (request.headers().content_type() == U("application/json")) + { + data = request.extract_json().get(); + } + + mitk::RESTUtil::ParamMap headers; + auto begin = request.headers().begin(); + auto end = request.headers().end(); + + for (; begin != end; ++begin) + { + headers.insert(mitk::RESTUtil::ParamMap::value_type(begin->first, begin->second)); + } + + response = manager->Handle(builder.to_uri(), data, request.method(), headers); } } - request.reply(content.is_null() - ? status_codes::NotFound - : status_codes::OK); + request.reply(response); } -} - +} // namespace mitk -mitk::RESTServer::RESTServer(const web::uri &uri) - : m_Impl{std::make_unique(uri)} -{ -} +mitk::RESTServer::RESTServer(const web::uri &uri) : m_Impl{std::make_unique(uri)} {} -mitk::RESTServer::~RESTServer() -{ -} +mitk::RESTServer::~RESTServer() {} void mitk::RESTServer::OpenListener() { m_Impl->listener = http_listener(m_Impl->uri); - m_Impl->listener.support(methods::GET, std::bind(&Impl::HandleGet, m_Impl.get(), _1)); + m_Impl->listener.support(std::bind(&Impl::HandleRequest, m_Impl.get(), _1)); + m_Impl->listener.support(methods::OPTIONS, std::bind(&Impl::HandleRequest, m_Impl.get(), _1)); m_Impl->listener.open().wait(); } void mitk::RESTServer::CloseListener() { m_Impl->listener.close().wait(); } web::uri mitk::RESTServer::GetUri() { return m_Impl->uri; } diff --git a/Modules/REST/test/mitkRESTClientTest.cpp b/Modules/REST/test/mitkRESTClientTest.cpp index 9c4f7c0123..e1dc882fb7 100644 --- a/Modules/REST/test/mitkRESTClientTest.cpp +++ b/Modules/REST/test/mitkRESTClientTest.cpp @@ -1,247 +1,266 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include #include #include +#include #include #include #include #include class mitkRESTClientTestSuite : public mitk::TestFixture, mitk::IRESTObserver { CPPUNIT_TEST_SUITE(mitkRESTClientTestSuite); - // MITK_TEST(GetRequestValidURI_ReturnsExpectedJSON); GET requests do not support content yet? - MITK_TEST(MultipleGetRequestValidURI_AllTasksFinish); - // MITK_TEST(PutRequestValidURI_ReturnsExpectedJSON); Does not work reliably on dart clients - // MITK_TEST(PostRequestValidURI_ReturnsExpectedJSON); -- " -- - MITK_TEST(GetRequestInvalidURI_ThrowsException); - MITK_TEST(PutRequestInvalidURI_ThrowsException); - MITK_TEST(PostRequestInvalidURI_ThrowsException); + // MITK_TEST(GetRequestValidURI_ReturnsExpectedJSON); GET requests do not support content yet? + MITK_TEST(MultipleGetRequestValidURI_AllTasksFinish); + // MITK_TEST(PutRequestValidURI_ReturnsExpectedJSON); Does not work reliably on dart clients + // MITK_TEST(PostRequestValidURI_ReturnsExpectedJSON); -- " -- + MITK_TEST(GetRequestInvalidURI_ThrowsException); + MITK_TEST(PutRequestInvalidURI_ThrowsException); + MITK_TEST(PostRequestInvalidURI_ThrowsException); CPPUNIT_TEST_SUITE_END(); public: mitk::IRESTManager *m_Service; web::json::value m_Data; - web::json::value Notify(const web::uri &, const web::json::value &) override + web::http::http_response Notify(const web::uri &, + const web::json::value &, + const web::http::method &, + const mitk::RESTUtil::ParamMap &headers) override { - return m_Data; + auto response = web::http::http_response(); + response.set_body(m_Data); + response.set_status_code(web::http::status_codes::OK); + + return response; } /** * @brief Setup Always call this method before each Test-case to ensure correct and new intialization of the used * members for a new test case. (If the members are not used in a test, the method does not need to be called). */ void setUp() override { m_Data = web::json::value(); - m_Data[_XPLATSTR("userId")] = web::json::value(1); - m_Data[_XPLATSTR("id")] = web::json::value(1); - m_Data[_XPLATSTR("title")] = web::json::value(U("this is a title")); - m_Data[_XPLATSTR("body")] = web::json::value(U("this is a body")); + m_Data[U("userId")] = web::json::value(1); + m_Data[U("id")] = web::json::value(1); + m_Data[U("title")] = web::json::value(U("this is a title")); + m_Data[U("body")] = web::json::value(U("this is a body")); us::ServiceReference serviceRef = us::GetModuleContext()->GetServiceReference(); if (serviceRef) { m_Service = us::GetModuleContext()->GetService(serviceRef); } if (!m_Service) { CPPUNIT_FAIL("Getting Service in setUp() failed"); } - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/clienttest"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/clienttest"), this); } - void tearDown() override - { - m_Service->HandleDeleteObserver(this); - } + void tearDown() override { m_Service->HandleDeleteObserver(this); } void GetRequestValidURI_ReturnsExpectedJSON() { web::json::value result; - m_Service->SendRequest(_XPLATSTR("http://localhost:8080/clienttest")) + m_Service->SendRequest(U("http://localhost:8080/clienttest")) .then([&](pplx::task resultTask) { try { result = resultTask.get(); } catch (const mitk::Exception &exception) { MITK_ERROR << exception.what(); return; } }) .wait(); CPPUNIT_ASSERT_MESSAGE("Result is the expected JSON value", result == m_Data); } void MultipleGetRequestValidURI_AllTasksFinish() { int count = 0; - // Create multiple tasks e.g. as shown below + // Create multiple tasks e.g. as shown below std::vector> tasks; for (int i = 0; i < 20; ++i) { - pplx::task singleTask = - m_Service->SendRequest(_XPLATSTR("http://localhost:8080/clienttest")) - .then([&](pplx::task resultTask) { - // Do something when a single task is done - try - { - resultTask.get(); - count +=1; - } - catch (const mitk::Exception &exception) - { - MITK_ERROR << exception.what(); - return; - } - }); + pplx::task singleTask = m_Service->SendRequest(U("http://localhost:8080/clienttest")) + .then([&](pplx::task resultTask) { + // Do something when a single task is done + try + { + resultTask.get(); + count += 1; + } + catch (const mitk::Exception &exception) + { + MITK_ERROR << exception.what(); + return; + } + }); tasks.emplace_back(singleTask); } - // Create a joinTask which includes all tasks you've created - auto joinTask = pplx::when_all(begin(tasks), end(tasks)); - // Run asynchonously - joinTask.then([&](pplx::task resultTask) { + // Create a joinTask which includes all tasks you've created + auto joinTask = pplx::when_all(begin(tasks), end(tasks)); + // Run asynchonously + joinTask + .then([&](pplx::task resultTask) { // Do something when all tasks are finished try { resultTask.get(); count += 1; } catch (const mitk::Exception &exception) { MITK_ERROR << exception.what(); return; } - }).wait(); + }) + .wait(); CPPUNIT_ASSERT_MESSAGE("Multiple Requests", 21 == count); } void PutRequestValidURI_ReturnsExpectedJSON() { // optional: link might get invalid or content is changed web::json::value result; m_Service - ->SendRequest(_XPLATSTR("https://jsonplaceholder.typicode.com/posts/1"), mitk::IRESTManager::RequestType::Put, &m_Data) + ->SendJSONRequest( + U("https://jsonplaceholder.typicode.com/posts/1"), mitk::IRESTManager::RequestType::Put, &web::json::value()) .then([&](pplx::task resultTask) { try { result = resultTask.get(); } catch (const mitk::Exception &exception) { MITK_ERROR << exception.what(); return; } }) .wait(); CPPUNIT_ASSERT_MESSAGE( "Result is the expected JSON value, check if the link is still valid since this is an optional test", result == m_Data); } void PostRequestValidURI_ReturnsExpectedJSON() { // optional: link might get invalid or content is changed web::json::value result; web::json::value data; - data[_XPLATSTR("userId")] = m_Data[_XPLATSTR("userId")]; - data[_XPLATSTR("title")] = m_Data[_XPLATSTR("title")]; - data[_XPLATSTR("body")] = m_Data[_XPLATSTR("body")]; + data[U("userId")] = m_Data[U("userId")]; + data[U("title")] = m_Data[U("title")]; + data[U("body")] = m_Data[U("body")]; - m_Service->SendRequest(_XPLATSTR("https://jsonplaceholder.typicode.com/posts"), mitk::IRESTManager::RequestType::Post, &data) + m_Service + ->SendJSONRequest(U("https://jsonplaceholder.typicode.com/posts"), mitk::IRESTManager::RequestType::Post, &data) .then([&](pplx::task resultTask) { try { result = resultTask.get(); } catch (const mitk::Exception &exception) { MITK_ERROR << exception.what(); return; } }) .wait(); - data[_XPLATSTR("id")] = web::json::value(101); + data[U("id")] = web::json::value(101); CPPUNIT_ASSERT_MESSAGE( "Result is the expected JSON value, check if the link is still valid since this is an optional test", result == data); } + void PostRequestHeaders_Success() + { + mitk::RESTUtil::ParamMap headers; + headers.insert(mitk::RESTUtil::ParamMap::value_type( + U("Content-Type"), U("multipart/related; type=\"application/dicom\"; boundary=boundary"))); + + m_Service->SendRequest(U("http://localhost:8080/clienttest")).then([&](pplx::task resultTask) { + // Do something when a single task is done + try + { + resultTask.get(); + } + catch (const mitk::Exception &exception) + { + MITK_ERROR << exception.what(); + return; + } + }); + } + void GetException() { - //Method which makes a get request to an invalid uri + // Method which makes a get request to an invalid uri web::json::value result; - m_Service->SendRequest(_XPLATSTR("http://localhost:1234/invalid")) + m_Service->SendRequest(U("http://localhost:1234/invalid")) .then([&](pplx::task resultTask) { result = resultTask.get(); }) .wait(); } void GetRequestInvalidURI_ThrowsException() { CPPUNIT_ASSERT_THROW(GetException(), mitk::Exception); } void PutException() { - //Method which makes a put request to an invalid uri + // Method which makes a put request to an invalid uri web::json::value result; - m_Service->SendRequest(_XPLATSTR("http://localhost:1234/invalid"), mitk::IRESTManager::RequestType::Put, &m_Data) - .then([&](pplx::task resultTask) { - result = resultTask.get();}) + m_Service->SendJSONRequest(U("http://localhost:1234/invalid"), mitk::IRESTManager::RequestType::Put, &m_Data) + .then([&](pplx::task resultTask) { result = resultTask.get(); }) .wait(); } - void PutRequestInvalidURI_ThrowsException() - { - CPPUNIT_ASSERT_THROW(PutException(), mitk::Exception); - } + void PutRequestInvalidURI_ThrowsException() { CPPUNIT_ASSERT_THROW(PutException(), mitk::Exception); } void PostException() { - //Method which makes a post request to an invalid uri + // Method which makes a post request to an invalid uri web::json::value result; - m_Service->SendRequest(_XPLATSTR("http://localhost:1234/invalid"), mitk::IRESTManager::RequestType::Post, &m_Data) - .then([&](pplx::task resultTask) { - result = resultTask.get(); - }) + m_Service->SendJSONRequest(U("http://localhost:1234/invalid"), mitk::IRESTManager::RequestType::Post, &m_Data) + .then([&](pplx::task resultTask) { result = resultTask.get(); }) .wait(); } - void PostRequestInvalidURI_ThrowsException() - { - CPPUNIT_ASSERT_THROW(PostException(), mitk::Exception); - } + void PostRequestInvalidURI_ThrowsException() { CPPUNIT_ASSERT_THROW(PostException(), mitk::Exception); } }; MITK_TEST_SUITE_REGISTRATION(mitkRESTClient) diff --git a/Modules/REST/test/mitkRESTServerTest.cpp b/Modules/REST/test/mitkRESTServerTest.cpp index a386dba712..cf3ddd496c 100644 --- a/Modules/REST/test/mitkRESTServerTest.cpp +++ b/Modules/REST/test/mitkRESTServerTest.cpp @@ -1,227 +1,256 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifdef _WIN32 - #include +#include #endif #include #include #include #include #include #include #include #include #include class mitkRESTServerTestSuite : public mitk::TestFixture, mitk::IRESTObserver { CPPUNIT_TEST_SUITE(mitkRESTServerTestSuite); - MITK_TEST(OpenListener_Succeed); - MITK_TEST(TwoListenerSameHostSamePort_OnlyOneOpened); - MITK_TEST(CloseListener_Succeed); - MITK_TEST(OpenMultipleListenerCloseOne_Succeed); - MITK_TEST(OpenMultipleListenerCloseAll_Succeed); - // MITK_TEST(OpenListenerGetRequestSamePath_ReturnExpectedJSON); GET requests do not support content yet? - MITK_TEST(CloseListener_NoRequestPossible); - MITK_TEST(OpenListenerGetRequestDifferentPath_ReturnNotFound); - MITK_TEST(OpenListenerCloseAndReopen_Succeed); + MITK_TEST(OpenListener_Succeed); + MITK_TEST(TwoListenerSameHostSamePort_OnlyOneOpened); + MITK_TEST(CloseListener_Succeed); + MITK_TEST(OpenMultipleListenerCloseOne_Succeed); + MITK_TEST(OpenMultipleListenerCloseAll_Succeed); + // MITK_TEST(OpenListenerGetRequestSamePath_ReturnExpectedJSON); GET requests do not support content yet? + MITK_TEST(CloseListener_NoRequestPossible); + MITK_TEST(OpenListenerGetRequestDifferentPath_ReturnNotFound); + MITK_TEST(OpenListenerCloseAndReopen_Succeed); + MITK_TEST(HandleHeader_Succeed); CPPUNIT_TEST_SUITE_END(); public: mitk::IRESTManager *m_Service; web::json::value m_Data; - web::json::value Notify(const web::uri &, const web::json::value &) override + web::http::http_response Notify(const web::uri &, + const web::json::value &, + const web::http::method &, + const mitk::RESTUtil::ParamMap &headers) override { - return m_Data; + auto response = web::http::http_response(); + response.set_body(m_Data); + mitk::RESTUtil::ParamMap::const_iterator contentTypePos = headers.find(U("Content-Type")); + if (contentTypePos != headers.end() && contentTypePos->second == U("awesome/type")) + { + m_Data[U("result")] = web::json::value(U("awesome/type")); + } + + return response; } /** * @brief Setup Always call this method before each Test-case to ensure correct and new intialization of the used * members for a new test case. (If the members are not used in a test, the method does not need to be called). */ void setUp() override { m_Data = web::json::value(); - m_Data[_XPLATSTR("userId")] = web::json::value(1); - m_Data[_XPLATSTR("id")] = web::json::value(1); - m_Data[_XPLATSTR("title")] = web::json::value(U("this is a title")); - m_Data[_XPLATSTR("body")] = web::json::value(U("this is a body")); + m_Data[U("userId")] = web::json::value(1); + m_Data[U("id")] = web::json::value(1); + m_Data[U("title")] = web::json::value(U("this is a title")); + m_Data[U("body")] = web::json::value(U("this is a body")); auto serviceRef = us::GetModuleContext()->GetServiceReference(); if (serviceRef) m_Service = us::GetModuleContext()->GetService(serviceRef); if (!m_Service) CPPUNIT_FAIL("Getting Service in setUp() failed"); } - void tearDown() override - { - m_Service->HandleDeleteObserver(this); - } + void tearDown() override { m_Service->HandleDeleteObserver(this); } void OpenListener_Succeed() { - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/servertest"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); CPPUNIT_ASSERT_MESSAGE("Open one listener, observer map size is one", 1 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Open one listener, server map size is one", 1 == m_Service->GetServerMap().size()); } void TwoListenerSameHostSamePort_OnlyOneOpened() { - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/servertest"), this); - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/serverexample"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/serverexample"), this); CPPUNIT_ASSERT_MESSAGE("Open two listener with a different path,same host, same port, observer map size is two", 2 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Open two listener with a different path,same host, same port, server map size is one", 1 == m_Service->GetServerMap().size()); } void CloseListener_Succeed() { - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/servertest"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); CPPUNIT_ASSERT_MESSAGE("Open one listener, observer map size is one", 1 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Open one listener, server map size is one", 1 == m_Service->GetServerMap().size()); m_Service->HandleDeleteObserver(this); CPPUNIT_ASSERT_MESSAGE("Closed listener, observer map is empty", 0 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Closed listener, server map is empty", 0 == m_Service->GetServerMap().size()); } void OpenMultipleListenerCloseOne_Succeed() { - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/servertest"), this); - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8090/serverexample"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); + m_Service->ReceiveRequest(U("http://localhost:8090/serverexample"), this); CPPUNIT_ASSERT_MESSAGE("Open two listener, observer map size is two", 2 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Open two listener, server map size is two", 2 == m_Service->GetServerMap().size()); - m_Service->HandleDeleteObserver(this, _XPLATSTR("http://localhost:8080/servertest")); + m_Service->HandleDeleteObserver(this, U("http://localhost:8080/servertest")); CPPUNIT_ASSERT_MESSAGE("Closed one of two listeners, observer map is size is one", 1 == m_Service->GetObservers().size()); - CPPUNIT_ASSERT_MESSAGE("Closed one of two listener, server map size is one", - 1 == m_Service->GetServerMap().size()); + CPPUNIT_ASSERT_MESSAGE("Closed one of two listener, server map size is one", 1 == m_Service->GetServerMap().size()); } void OpenMultipleListenerCloseAll_Succeed() { - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/servertest"), this); - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8090/serverexample"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); + m_Service->ReceiveRequest(U("http://localhost:8090/serverexample"), this); CPPUNIT_ASSERT_MESSAGE("Open two listener, observer map size is two", 2 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Open two listener, server map size is two", 2 == m_Service->GetServerMap().size()); m_Service->HandleDeleteObserver(this); CPPUNIT_ASSERT_MESSAGE("Closed all listeners, observer map is empty", 0 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Closed all listeners, server map is empty", 0 == m_Service->GetServerMap().size()); } void OpenListenerGetRequestSamePath_ReturnExpectedJSON() { - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/servertest"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); web::json::value result; - - m_Service->SendRequest(_XPLATSTR("http://localhost:8080/servertest")) + auto body = web::json::value(); + m_Service->SendRequest(U("http://localhost:8080/servertest")) .then([&](pplx::task resultTask) { try { result = resultTask.get(); } catch (const mitk::Exception &exception) { MITK_ERROR << exception.what(); return; } }) .wait(); CPPUNIT_ASSERT_MESSAGE("Opened listener and send request to same uri, returned expected JSON", result == m_Data); } void RequestToClosedListener() { web::json::value result; - m_Service->SendRequest(_XPLATSTR("http://localhost:8080/servertest")) + m_Service->SendRequest(U("http://localhost:8080/servertest")) .then([&](pplx::task resultTask) { result = resultTask.get(); }) .wait(); } void CloseListener_NoRequestPossible() { - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/servertest"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); CPPUNIT_ASSERT_MESSAGE("Open one listener, observer map size is one", 1 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Open one listener, server map size is one", 1 == m_Service->GetServerMap().size()); m_Service->HandleDeleteObserver(this); CPPUNIT_ASSERT_MESSAGE("Closed listener, observer map is empty", 0 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Closed listener, server map is empty", 0 == m_Service->GetServerMap().size()); CPPUNIT_ASSERT_THROW(RequestToClosedListener(), mitk::Exception); } void RequestToDifferentPathNotFound() { - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/servertest"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); web::json::value result; - m_Service->SendRequest(_XPLATSTR("http://localhost:8080/serverexample")) + m_Service->SendRequest(U("http://localhost:8080/serverexample")) .then([&](pplx::task resultTask) { result = resultTask.get(); }) .wait(); } void OpenListenerGetRequestDifferentPath_ReturnNotFound() { CPPUNIT_ASSERT_THROW(RequestToDifferentPathNotFound(), mitk::Exception); } void OpenListenerCloseAndReopen_Succeed() { - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/servertest"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); CPPUNIT_ASSERT_MESSAGE("Open one listener, observer map size is one", 1 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Open one listener, server map size is one", 1 == m_Service->GetServerMap().size()); m_Service->HandleDeleteObserver(this); CPPUNIT_ASSERT_MESSAGE("Closed listener, observer map is empty", 0 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Closed listener, server map is empty", 0 == m_Service->GetServerMap().size()); - m_Service->ReceiveRequest(_XPLATSTR("http://localhost:8080/servertest"), this); + m_Service->ReceiveRequest(U("http://localhost:8080/servertest"), this); CPPUNIT_ASSERT_MESSAGE("Reopened listener, observer map size is one", 1 == m_Service->GetObservers().size()); CPPUNIT_ASSERT_MESSAGE("Reopened listener, server map size is one", 1 == m_Service->GetServerMap().size()); } + + void HandleHeader_Succeed() + { + mitk::RESTUtil::ParamMap headers; + headers.insert(mitk::RESTUtil::ParamMap::value_type(U("Content-Type"), U("awesome/type"))); + + m_Service->SendRequest(U("http://localhost:8080/clienttest")).then([&](pplx::task resultTask) { + // Do something when a single task is done + try + { + auto result = resultTask.get(); + CPPUNIT_ASSERT_MESSAGE("Sent Header is not successfull transfered to server", + result[U("result")].as_string() == U("awesome/type")); + } + catch (const mitk::Exception &exception) + { + MITK_ERROR << exception.what(); + return; + } + }); + } }; MITK_TEST_SUITE_REGISTRATION(mitkRESTServer) diff --git a/Modules/RESTService/include/mitkRESTManager.h b/Modules/RESTService/include/mitkRESTManager.h index ea50ffdeea..cd0929d0ac 100644 --- a/Modules/RESTService/include/mitkRESTManager.h +++ b/Modules/RESTService/include/mitkRESTManager.h @@ -1,118 +1,157 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkRESTManager_h #define mitkRESTManager_h -#include #include +#include +#include namespace mitk { /** - * @class RESTManager - * @brief this is a microservice for managing REST-requests, used for non-qt applications. - * - * RESTManagerQt in the CppRestSdkQt module inherits from this class and is the equivalent microservice - * used for Qt applications. - */ + * @class RESTManager + * @brief this is a microservice for managing REST-requests, used for non-qt applications. + * + * RESTManagerQt in the CppRestSdkQt module inherits from this class and is the equivalent microservice + * used for Qt applications. + */ class MITKRESTSERVICE_EXPORT RESTManager : public IRESTManager { public: RESTManager(); ~RESTManager() override; + /** + * @brief Executes a HTTP request in the mitkRESTClient class + * + * @throw mitk::Exception if RequestType is not suported + * @param uri defines the URI the request is send to + * @param type the RequestType of the HTTP request (optional) + * @param headers the headers for the request (optional) + * @return task to wait for + */ + pplx::task SendRequest( + const web::uri &uri, + const RequestType &type = RequestType::Get, + const std::map headers = {}) override; + /** * @brief Executes a HTTP request in the mitkRESTClient class * * @throw mitk::Exception if RequestType is not suported * @param uri defines the URI the request is send to * @param type the RequestType of the HTTP request (optional) * @param body the body for the request (optional) - * @param filePath the file path to store the request to + * @param headers the headers for the request (optional) + * @param filePath the file path to store the request to (optional) * @return task to wait for */ - pplx::task SendRequest(const web::uri &uri, - const RequestType &type = RequestType::Get, - const web::json::value *body = nullptr, - const utility::string_t &filePath = {}) override; + pplx::task SendJSONRequest(const web::uri &uri, + const RequestType &type = RequestType::Get, + const web::json::value *body = nullptr, + const std::map headers = {}, + const utility::string_t &filePath = {}) override; + + /** + * @brief Executes a HTTP request in the mitkRESTClient class + * + * @throw mitk::Exception if RequestType is not suported + * @param uri defines the URI the request is send to + * @param type the RequestType of the HTTP request (optional) + * @param body the body for the request (optional) + * @param headers the headers for the request (optional) + * @return task to wait for + */ + pplx::task SendBinaryRequest( + const web::uri &uri, + const RequestType &type = RequestType::Get, + const std::vector * = {}, + const std::map headers = {}) override; /** * @brief starts listening for requests if there isn't another observer listening and the port is free * * @param uri defines the URI for which incoming requests should be send to the observer * @param observer the observer which handles the incoming requests */ void ReceiveRequest(const web::uri &uri, IRESTObserver *observer) override; /** * @brief Handles incoming requests by notifying the observer which should receive it * * @param uri defines the URI of the request * @param body the body of the request - * @return the data which is modified by the notified observer + * @param method the http method of the request + * @param headers the http headers of the request + * @return the response */ - web::json::value Handle(const web::uri &uri, const web::json::value &body) override; + + web::http::http_response Handle(const web::uri &uri, + const web::json::value &body, + const web::http::method &method, + const mitk::RESTUtil::ParamMap &headers) override; /** * @brief Handles the deletion of an observer for all or a specific uri * * @param observer the observer which shouldn't receive requests anymore * @param uri the uri for which the observer doesn't handle requests anymore (optional) */ void HandleDeleteObserver(IRESTObserver *observer, const web::uri &uri = {}) override; /** - * @brief internal use only - */ - const std::map& GetServerMap() override; - std::map, IRESTObserver *>& GetObservers() override; + * @brief internal use only + */ + const std::map &GetServerMap() override; + std::map, IRESTObserver *> &GetObservers() override; private: /** * @brief adds an observer if a port is free, called by ReceiveRequest method * * @param uri the uri which builds the key for the observer map * @param observer the observer which is added */ void AddObserver(const web::uri &uri, IRESTObserver *observer); /** * @brief handles server management if there is already a server under a port, called by ReceiveRequest method * * @param uri the uri which which is requested to be added * @param observer the observer which proceeds the request */ void RequestForATakenPort(const web::uri &uri, IRESTObserver *observer); /** * @brief deletes an observer, called by HandleDeleteObserver method * * @param it the iterator comparing the observers in HandleDeleteObserver method * @return bool if there is another observer under the port */ bool DeleteObserver(std::map, IRESTObserver *>::iterator &it); void SetServerMap(const int port, RESTServer *server); void DeleteFromServerMap(const int port); void SetObservers(const std::pair key, IRESTObserver *observer); - std::map m_ServerMap; // Map with port server pairs + std::map m_ServerMap; // Map with port server pairs std::map, IRESTObserver *> m_Observers; // Map with all observers }; -} +} // namespace mitk #endif diff --git a/Modules/RESTService/src/mitkRESTManager.cpp b/Modules/RESTService/src/mitkRESTManager.cpp index 711be3dc65..36b365168d 100644 --- a/Modules/RESTService/src/mitkRESTManager.cpp +++ b/Modules/RESTService/src/mitkRESTManager.cpp @@ -1,217 +1,262 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ -#include +#include #include +#include #include -#include #include #include -mitk::RESTManager::RESTManager() +mitk::RESTManager::RESTManager() {} + +mitk::RESTManager::~RESTManager() {} + +pplx::task mitk::RESTManager::SendRequest( + const web::uri &uri, const RequestType &type, const std::map headers) { + pplx::task answer; + auto client = new RESTClient; + + switch (type) + { + case RequestType::Get: + answer = client->Get(uri, headers); + break; + + default: + mitkThrow() << "Request Type not supported"; + } + + return answer; } -mitk::RESTManager::~RESTManager() +pplx::task mitk::RESTManager::SendBinaryRequest( + const web::uri &uri, + const RequestType &type, + const std::vector *content, + const std::map headers) { + pplx::task answer; + auto client = new RESTClient; + + switch (type) + { + case RequestType::Post: + if (nullptr == content) + MITK_WARN << "Content for post is empty, this will create an empty resource"; + + answer = client->Post(uri, content, headers); + break; + + default: + mitkThrow() << "Request Type not supported for binary data"; + } + + return answer; } -pplx::task mitk::RESTManager::SendRequest(const web::uri &uri, - const RequestType &type, - const web::json::value *content, - const utility::string_t &filePath) +pplx::task mitk::RESTManager::SendJSONRequest( + const web::uri &uri, + const RequestType &type, + const web::json::value *content, + const std::map headers, + const utility::string_t &filePath) { pplx::task answer; auto client = new RESTClient; switch (type) { case RequestType::Get: - answer = !filePath.empty() - ? client->Get(uri, filePath) - : client->Get(uri); + answer = !filePath.empty() ? client->Get(uri, filePath, headers) : client->Get(uri, headers); break; case RequestType::Post: if (nullptr == content) - MITK_WARN << "Content for put is empty, this will create an empty resource"; + MITK_WARN << "Content for post is empty, this will create an empty resource"; - answer = client->Post(uri, content); + answer = client->Post(uri, content, headers); break; case RequestType::Put: if (nullptr == content) MITK_WARN << "Content for put is empty, this will empty the ressource"; answer = client->Put(uri, content); break; default: mitkThrow() << "Request Type not supported"; } return answer; } void mitk::RESTManager::ReceiveRequest(const web::uri &uri, mitk::IRESTObserver *observer) { // New instance of RESTServer in m_ServerMap, key is port of the request auto port = uri.port(); // Checking if port is free to add a new Server if (0 == m_ServerMap.count(port)) { this->AddObserver(uri, observer); // creating server instance auto server = new RESTServer(uri.authority()); // add reference to server instance to map m_ServerMap[port] = server; // start Server server->OpenListener(); } // If there is already a server under this port else { this->RequestForATakenPort(uri, observer); } } -web::json::value mitk::RESTManager::Handle(const web::uri &uri, const web::json::value &body) +web::http::http_response mitk::RESTManager::Handle(const web::uri &uri, + const web::json::value &body, + const web::http::method &method, + const mitk::RESTUtil::ParamMap &headers) { // Checking if there is an observer for the port and path auto key = std::make_pair(uri.port(), uri.path()); if (0 != m_Observers.count(key)) { - return m_Observers[key]->Notify(uri, body); + return m_Observers[key]->Notify(uri, body, method, headers); } // No observer under this port, return null which results in status code 404 (s. RESTServer) else { MITK_WARN << "No Observer can handle the data"; - return web::json::value(); + web::http::http_response response(web::http::status_codes::BadGateway); + response.set_body(U("No one can handle the request under the given port.")); + return response; } } void mitk::RESTManager::HandleDeleteObserver(IRESTObserver *observer, const web::uri &uri) { for (auto it = m_Observers.begin(); it != m_Observers.end();) { mitk::IRESTObserver *obsMap = it->second; // Check wether observer is at this place in map if (observer == obsMap) { // Check wether it is the right uri to be deleted if (uri.is_empty() || uri.path() == it->first.second) { int port = it->first.first; bool noObserverForPort = this->DeleteObserver(it); if (noObserverForPort) { // there isn't an observer at this port, delete m_ServerMap entry for this port // close listener m_ServerMap[port]->CloseListener(); delete m_ServerMap[port]; // delete server from map m_ServerMap.erase(port); } } else { ++it; } } else { ++it; } } } const std::map &mitk::RESTManager::GetServerMap() { return m_ServerMap; } std::map, mitk::IRESTObserver *> &mitk::RESTManager::GetObservers() { return m_Observers; } void mitk::RESTManager::AddObserver(const web::uri &uri, IRESTObserver *observer) { // new observer has to be added std::pair key(uri.port(), uri.path()); m_Observers[key] = observer; } void mitk::RESTManager::RequestForATakenPort(const web::uri &uri, IRESTObserver *observer) { // Same host, means new observer but not a new server instance if (uri.authority() == m_ServerMap[uri.port()]->GetUri()) { // new observer has to be added std::pair key(uri.port(), uri.path()); // only add a new observer if there isn't already an observer for this uri if (0 == m_Observers.count(key)) { this->AddObserver(uri, observer); } else { MITK_ERROR << "Threre is already a observer handeling this data"; } } // Error, since another server can't be added under this port else { MITK_ERROR << "There is already another server listening under this port"; } } bool mitk::RESTManager::DeleteObserver(std::map, IRESTObserver *>::iterator &it) { int port = it->first.first; it = m_Observers.erase(it); for (auto observer : m_Observers) { if (port == observer.first.first) { // there still exists an observer for this port return false; } } return true; } void mitk::RESTManager::SetServerMap(const int port, RESTServer *server) { m_ServerMap[port] = server; } void mitk::RESTManager::DeleteFromServerMap(const int port) { m_ServerMap.erase(port); } void mitk::RESTManager::SetObservers(const std::pair key, IRESTObserver *observer) { m_Observers[key] = observer; }