From 51112eb7256c90724d4b75f3838adf9c1bfe3cd5 Mon Sep 17 00:00:00 2001 From: Andras Lasso Date: Thu, 16 Jan 2025 10:51:06 -0500 Subject: [PATCH] ENH: Many fixes and improvements --- Libs/MRML/Core/vtkCodedEntry.cxx | 45 + Libs/MRML/Core/vtkCodedEntry.h | 2 + Libs/MRML/Core/vtkMRMLColorNode.cxx | 115 +- Libs/MRML/Core/vtkMRMLColorNode.h | 56 +- Libs/MRML/Core/vtkMRMLColorTableNode.cxx | 71 +- Libs/MRML/Core/vtkMRMLColorTableNode.h | 10 +- .../Core/vtkMRMLColorTableStorageNode.cxx | 6 +- Libs/MRML/Core/vtkMRMLProceduralColorNode.cxx | 2 +- Libs/MRML/Core/vtkMRMLScene.cxx | 3 +- .../Testing/Cxx/vtkMRMLColorLogicTest1.cxx | 2 +- Libs/MRML/Logic/vtkMRMLColorLogic.cxx | 6 +- Libs/MRML/Widgets/qMRMLColorModel.cxx | 123 +- Libs/MRML/Widgets/qMRMLColorModel.h | 2 +- .../UI/qSlicerTerminologyEditorWidget.ui | 18 + .../qSlicerColorTableTerminologyDelegate.cxx | 8 +- .../qSlicerTerminologyEditorWidget.cxx | 30 +- .../Colors/qSlicerColorsModuleWidget.cxx | 26 +- .../vtkSlicerSegmentationsModuleLogic.cxx | 4 +- .../vtkSlicerTerminologiesModuleLogic.cxx | 160 +- .../Logic/vtkSlicerTerminologiesModuleLogic.h | 15 + .../Widgets/qMRMLSimpleColorTableView.cxx | 34 +- .../Widgets/qMRMLSimpleColorTableView.h | 7 +- .../qSlicerTerminologyItemDelegate.cxx | 8 +- .../qSlicerTerminologyNavigatorWidget.cxx | 3107 +++++++++-------- .../qSlicerTerminologyNavigatorWidget.h | 50 +- 25 files changed, 2130 insertions(+), 1780 deletions(-) diff --git a/Libs/MRML/Core/vtkCodedEntry.cxx b/Libs/MRML/Core/vtkCodedEntry.cxx index 221a986b5ab..64f34ff8bfd 100644 --- a/Libs/MRML/Core/vtkCodedEntry.cxx +++ b/Libs/MRML/Core/vtkCodedEntry.cxx @@ -178,3 +178,48 @@ bool vtkCodedEntry::SetFromString(const std::string& content) // CodeMeaning is optional return success; } + +//---------------------------------------------------------------------------- +bool vtkCodedEntry::AreEqual(vtkCodedEntry* entry1, vtkCodedEntry* entry2) +{ + if (entry1 == entry2) + { + // they are the same object or both nullptr + return true; + } + if (!entry1 || !entry2) + { + // only one is nullptr + return false; + } + // Neither entry is nullptr + if ((entry1->GetCodeValue() == nullptr) != (entry2->GetCodeValue() == nullptr)) + { + // Only one of them is nullptr + return false; + } + if (entry2->GetCodeValue() && entry1->GetCodeValue() && strcmp(entry2->GetCodeValue(), entry1->GetCodeValue()) != 0) + { + return false; + } + if ((entry1->GetCodingSchemeDesignator() == nullptr) != (entry2->GetCodingSchemeDesignator() == nullptr)) + { + // Only one of them is nullptr + return false; + } + if (entry2->GetCodingSchemeDesignator() && entry1->GetCodingSchemeDesignator() + && strcmp(entry2->GetCodingSchemeDesignator(), entry1->GetCodingSchemeDesignator()) != 0) + { + return false; + } + if ((entry1->GetCodeMeaning() == nullptr) != (entry2->GetCodeMeaning() == nullptr)) + { + // Only one of them is nullptr + return false; + } + if (entry2->GetCodeMeaning() && entry1->GetCodeMeaning() && strcmp(entry2->GetCodeMeaning(), entry1->GetCodeMeaning()) != 0) + { + return false; + } + return true; +} diff --git a/Libs/MRML/Core/vtkCodedEntry.h b/Libs/MRML/Core/vtkCodedEntry.h index df517a8d96f..fd3245e71ec 100644 --- a/Libs/MRML/Core/vtkCodedEntry.h +++ b/Libs/MRML/Core/vtkCodedEntry.h @@ -87,6 +87,8 @@ class VTK_MRML_EXPORT vtkCodedEntry : public vtkObject /// \return true on success bool SetFromString(const std::string& content); + static bool AreEqual(vtkCodedEntry* entry1, vtkCodedEntry* entry2); + protected: vtkCodedEntry(); ~vtkCodedEntry() override; diff --git a/Libs/MRML/Core/vtkMRMLColorNode.cxx b/Libs/MRML/Core/vtkMRMLColorNode.cxx index 27459237d13..c6a7dc17880 100644 --- a/Libs/MRML/Core/vtkMRMLColorNode.cxx +++ b/Libs/MRML/Core/vtkMRMLColorNode.cxx @@ -166,7 +166,6 @@ void vtkMRMLColorNode::PrintSelf(ostream& os, vtkIndent indent) } os << indent << indent << i << " "; os << this->GetColorName(i); - os << " (" << vtkMRMLColorNode::GetNameSourceAsString(prop.NameSource) << ")"; double color[4]; this->GetColor(i, color); os << " (" << color[0] << ", " << color[1] << ", " << color[2] << ", " << color[3] << ")"; @@ -278,7 +277,7 @@ bool vtkMRMLColorNode::SetNameFromColor(int index) ss.setf(std::ios::fixed, std::ios::floatfield); ss << "R=" << rgba[0] << " G=" << rgba[1] << " B=" << rgba[2] << " A=" << rgba[3]; vtkDebugMacro("SetNamesFromColors: " << index << " Name = " << ss.str().c_str()); - if (this->SetColorName(index, ss.str().c_str(), NameAutoGeneratedFromColor) == 0) + if (this->SetColorName(index, ss.str().c_str())) { vtkErrorMacro("SetNamesFromColors: Error setting color name " << index << " Name = " << ss.str().c_str()); return false; @@ -309,45 +308,45 @@ bool vtkMRMLColorNode::GetColorDefined(int index) { return false; } - // For improved backward compatibility with older extensions, - // if the color name is NoName name then the we consider the color defined. - if (this->GetNoName() && this->GetColorName(index) && - strcmp(this->GetNoName(), this->GetColorName(index)) == 0) - { - return false; - } return true; } //--------------------------------------------------------------------------- -bool vtkMRMLColorNode::SetColorDefined(int index, bool defined) +const char *vtkMRMLColorNode::GetColorName(int index) { + // Do not use GetProperty because the content of the locally copied property's std::string does not survive the return if (index < 0 || index >= (int)this->Properties.size()) { - return false; + return ""; } - if (this->Properties[index].Defined != defined) + if (!this->Properties[index].Defined) { - this->Properties[index].Defined = defined; - this->StorableModifiedTime.Modified(); - this->Modified(); + return this->NoName; } - return true; + return this->Properties[index].Name.c_str(); } //--------------------------------------------------------------------------- -const char *vtkMRMLColorNode::GetColorName(int index) +bool vtkMRMLColorNode::GetColorNameDefined(int index) { - // Do not use GetProperty because the content of the locally copied property's std::string does not survive the return if (index < 0 || index >= (int)this->Properties.size()) { - return ""; + vtkWarningMacro("GetColorNameDefined failed: index " << index << " out of range (0-" << (this->Properties.size() - 1)); + return false; } - if (!this->Properties[index].Defined) + const char* name = this->GetColorName(index); + if (name == nullptr) { - return this->NoName; + return false; } - return this->Properties[index].Name.c_str(); + // Return false if name equals NoName + // for improved backward compatibility with older extensions. + if (this->GetNoName() && strcmp(this->GetNoName(), name) == 0) + { + return false; + } + // return true if name is not empty + return strlen(name) > 0; } //--------------------------------------------------------------------------- @@ -574,10 +573,15 @@ bool vtkMRMLColorNode::SetTerminologyFromString(int ind, std::string terminology } } - this->Properties[ind] = prop; + if (this->Properties[ind] != prop) + { + this->Properties[ind] = prop; + this->StorableModifiedTime.Modified(); + this->Modified(); + } // Set attribute indicating that the color table contains terminology - this->SetAttribute(this->GetContainTerminologyAttributeName(), "true"); + this->SetContainsTerminology(true); return true; } @@ -643,7 +647,7 @@ std::string vtkMRMLColorNode::GetColorNameAsFileName(int colorIndex, const char } //--------------------------------------------------------------------------- -int vtkMRMLColorNode::SetColorName(int ind, const char *name, NameSourceType source/*=NameSetManually*/) +int vtkMRMLColorNode::SetColorName(int ind, const char *name) { if (ind >= static_cast(this->Properties.size()) || ind < 0) { @@ -651,13 +655,33 @@ int vtkMRMLColorNode::SetColorName(int ind, const char *name, NameSourceType sou << this->Properties.size() << ", table name = " << (this->GetName() == nullptr ? "null" : this->GetName())); return 0; } - std::string newName(name); + std::string colorName; + bool colorDefined = true; + if (name) + { + // For improved backward compatibility with older extensions, + // if the color name is NoName name then the we consider the color undefined. + if (this->GetNoName() && strcmp(this->GetNoName(), name) == 0) + { + colorDefined = false; + } + else + { + colorName = name; + } + } PropertyType& prop = this->Properties[ind]; - if (prop.Name != newName || prop.NameSource != source || !prop.Defined) + if (prop.Name != colorName || prop.Defined != colorDefined) { - prop.Name = newName; - prop.NameSource = source; - prop.Defined = true; + if (colorDefined) + { + prop.Name = colorName; + prop.Defined = colorDefined; + } + else + { + prop.Clear(); + } this->StorableModifiedTime.Modified(); this->Modified(); } @@ -665,7 +689,7 @@ int vtkMRMLColorNode::SetColorName(int ind, const char *name, NameSourceType sou } //--------------------------------------------------------------------------- -int vtkMRMLColorNode::SetColorNameWithSpaces(int ind, const char *name, const char *subst, NameSourceType source/*=NameSetManually*/) +int vtkMRMLColorNode::SetColorNameWithSpaces(int ind, const char *name, const char *subst) { std::string nameString = std::string(name); std::string substString = std::string(subst); @@ -673,12 +697,12 @@ int vtkMRMLColorNode::SetColorNameWithSpaces(int ind, const char *name, const ch if (strstr(name, substString.c_str()) != nullptr) { std::replace(nameString.begin(), nameString.end(), *subst, ' '); - return this->SetColorName(ind, nameString.c_str(), source); + return this->SetColorName(ind, nameString.c_str()); } else { // no substitutions necessary - return this->SetColorName(ind, name, source); + return this->SetColorName(ind, name); } } @@ -712,25 +736,20 @@ vtkLookupTable* vtkMRMLColorNode::CreateLookupTableCopy() } //--------------------------------------------------------------------------- -/*static*/ std::string vtkMRMLColorNode::GetNameSourceAsString(NameSourceType nameSource) +bool vtkMRMLColorNode::GetContainsTerminology() { - switch (nameSource) - { - case NameSourceUnknown: return "unknown"; - case NameSetManually: return "setManually"; - case NameAutoGeneratedFromColor: return "autoGeneratedFromColor"; - case NameAutoGeneratedFromTerminology: return "autoGeneratedFromTerminology"; - } - return "invalid"; + return (this->GetAttribute(this->GetContainsTerminologyAttributeName()) != nullptr); } //--------------------------------------------------------------------------- -int vtkMRMLColorNode::GetColorNameSource(int index) +void vtkMRMLColorNode::SetContainsTerminology(bool containsTerminology) { - if (index < 0 || index >= (int)this->Properties.size()) + if (containsTerminology) { - vtkWarningMacro("GetColorNameSource failed: index " << index << " out og range (0-" << (this->Properties.size() - 1)); - return NameSourceUnknown; + this->SetAttribute(this->GetContainsTerminologyAttributeName(), "true"); } - return this->Properties[index].NameSource; -} + else + { + this->RemoveAttribute(this->GetContainsTerminologyAttributeName()); + } +} \ No newline at end of file diff --git a/Libs/MRML/Core/vtkMRMLColorNode.h b/Libs/MRML/Core/vtkMRMLColorNode.h index c0b1cea9ee3..b1f6ce7c922 100644 --- a/Libs/MRML/Core/vtkMRMLColorNode.h +++ b/Libs/MRML/Core/vtkMRMLColorNode.h @@ -45,14 +45,6 @@ class VTK_MRML_EXPORT vtkMRMLColorNode : public vtkMRMLStorableNode vtkTypeMacro(vtkMRMLColorNode,vtkMRMLStorableNode); void PrintSelf(ostream& os, vtkIndent indent) override; - enum NameSourceType - { - NameSourceUnknown, ///< Source of name is unknown - NameSetManually, ///< User manually set this name - NameAutoGeneratedFromColor, ///< Name is automatically generated from color - NameAutoGeneratedFromTerminology ///< Name is automatically generated from terminology - }; - vtkMRMLNode* CreateNodeInstance() override = 0; //-------------------------------------------------------------------------- @@ -114,21 +106,16 @@ class VTK_MRML_EXPORT vtkMRMLColorNode : public vtkMRMLStorableNode /// Returns true if the entry is defined. bool GetColorDefined(int ind); - /// Low-level method to set the defined flag for an entry. Normally this flag does not have to be modified manually. - bool SetColorDefined(int index, bool defined); /// Get name of a color from its index (index is 0-based) /// Return NoNameX if color is undefined. /// Return empty string if index is out of range. /// \sa GetColorIndexByName() const char* GetColorName(int ind); - - /// Get whether color name for given index was auto-generated or set manually by the user. - /// \sa NameSourceType - int GetColorNameSource(int ind); - - /// Static utility method to get name source type as string. - static std::string GetNameSourceAsString(NameSourceType nameSource); + /// Returns true if the color name is defined. + /// Currently the name is considered to be defined if it is not empty and differs from NoName. + /// In the future, NoName will be deprecated and the name will be considered defined if it is not empty. + bool GetColorNameDefined(int ind); /// Return the index associated with this color name, which can then be used /// to get the color. Returns -1 on failure. @@ -155,14 +142,14 @@ class VTK_MRML_EXPORT vtkMRMLColorNode : public vtkMRMLStorableNode /// Set the name of the n-th entry /// \return 1 on success, 0 on failure. - int SetColorName(int ind, const char *name, NameSourceType source=NameSetManually); + int SetColorName(int ind, const char *name); /// /// \deprecated SetColorNameWithSpaces /// The method is no longer needed and will be removed in the future. /// Set the name of the n-th entry, replacing the subst character by space. /// Returns 1 on success, 0 on failure - int SetColorNameWithSpaces(int ind, const char *name, const char *subst, NameSourceType source=NameSetManually); + int SetColorNameWithSpaces(int ind, const char *name, const char *subst); vtkCodedEntry* GetTerminologyCategory(int ind); vtkCodedEntry* GetTerminologyType(int ind); @@ -177,7 +164,9 @@ class VTK_MRML_EXPORT vtkMRMLColorNode : public vtkMRMLStorableNode bool SetTerminologyFromString(int ind, std::string terminologyString); /// Get attribute name that indicates if the color table has terminology entries. - const char* GetContainTerminologyAttributeName() { return "ContainTerminology"; }; + const char* GetContainsTerminologyAttributeName() { return "ContainsTerminology"; }; + /// Returns true if the color table has terminology information. + bool GetContainsTerminology(); /// Get the number of colors in the table virtual int GetNumberOfColors() = 0; @@ -201,6 +190,10 @@ class VTK_MRML_EXPORT vtkMRMLColorNode : public vtkMRMLStorableNode vtkSetStringMacro(NoName); /// Set values in the names vector from the colors in the node + /// It is not recommended to use this method, because it makes the name and color columns of the table + /// store redundant information, which may get out of sync. + /// Saving automatically-generated color names in the table also interferes with automatically updating + /// the displayed table when terminology is specified or when displaying the color table in an internationalized GUI. void SetNamesFromColors(); /// \sa vtkMRMLStorableNode::GetModifiedSinceRead() @@ -229,14 +222,15 @@ class VTK_MRML_EXPORT vtkMRMLColorNode : public vtkMRMLStorableNode vtkMRMLColorNode(const vtkMRMLColorNode&); void operator=(const vtkMRMLColorNode&); + /// Sets whether the color table has terminology information. + void SetContainsTerminology(bool containsTerminology); + struct PropertyType { // The label value and corresponding color has been explicitly set. // If the label value is not included in the color table file then "Defined" will set to false. bool Defined{ false }; std::string Name; - // Name is automatically generated (e.g., from color) - NameSourceType NameSource{ NameSourceUnknown }; vtkSmartPointer Category; vtkSmartPointer Type; vtkSmartPointer TypeModifier; @@ -246,7 +240,6 @@ class VTK_MRML_EXPORT vtkMRMLColorNode : public vtkMRMLStorableNode { this->Defined = source.Defined; this->Name = source.Name; - this->NameSource = source.NameSource; this->Category = source.Category; this->Type = source.Type; this->TypeModifier = source.TypeModifier; @@ -257,19 +250,32 @@ class VTK_MRML_EXPORT vtkMRMLColorNode : public vtkMRMLStorableNode { this->Defined = false; this->Name.clear(); - this->NameSource = NameSourceUnknown; this->Category = nullptr; this->Type = nullptr; this->TypeModifier = nullptr; this->Region = nullptr; this->RegionModifier = nullptr; } + bool operator==(const PropertyType& other) const + { + return this->Defined == other.Defined && + this->Name == other.Name && + vtkCodedEntry::AreEqual(this->Category, other.Category) && + vtkCodedEntry::AreEqual(this->Type, other.Type) && + vtkCodedEntry::AreEqual(this->TypeModifier, other.TypeModifier) && + vtkCodedEntry::AreEqual(this->Region, other.Region) && + vtkCodedEntry::AreEqual(this->RegionModifier, other.RegionModifier); + } + bool operator!=(const PropertyType& other) const + { + return !(*this == other); + } }; bool GetProperty(int ind, PropertyType& prop); /// - /// Set values in the names vector from the colors in the node + /// Set values in the names vector from the colors in the node. virtual bool SetNameFromColor(int index); /// Which type of color information does this node hold? diff --git a/Libs/MRML/Core/vtkMRMLColorTableNode.cxx b/Libs/MRML/Core/vtkMRMLColorTableNode.cxx index f68ff03d023..cb78bd3c866 100644 --- a/Libs/MRML/Core/vtkMRMLColorTableNode.cxx +++ b/Libs/MRML/Core/vtkMRMLColorTableNode.cxx @@ -110,7 +110,7 @@ void vtkMRMLColorTableNode::ReadXMLAttributes(const char** atts) } std::replace(name.begin(), name.end(), '_', ' '); - if (this->SetColorName(index, name.c_str(), NameSetManually) != 0) + if (this->SetColorName(index, name.c_str()) != 0) { this->LookupTable->SetTableValue(index, r, g, b, a); } @@ -613,7 +613,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A full rainbow of 256 colors, goes from red to red with all rainbow colors in between. Useful for colorful display of a label map"); } else if (this->Type == this->Grey) @@ -626,7 +625,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A grey scale ranging from black at 0 to white at 255. Useful for displaying MRI volumes."); } else if (this->Type == this->Red) @@ -639,7 +637,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A red scale of 256 values. Useful for layering with Cyan"); } else if (this->Type == this->Green) @@ -652,7 +649,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A green scale of 256 values, useful for layering with Magenta"); } else if (this->Type == this->Blue) @@ -665,7 +661,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A blue scale of 256 values from black to pure blue, useful for layering with Yellow"); } else if (this->Type == this->Yellow) @@ -678,7 +673,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A yellow scale of 256 values, from black to pure yellow, useful for layering with blue (it's complementary, layering yields gray)"); } else if (this->Type == this->Cyan) @@ -691,7 +685,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A cyan ramp of 256 values, from black to cyan, complementary ramp to red, layering yields gray"); } else if (this->Type == this->Magenta) @@ -704,7 +697,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A magenta scale of 256 colors from black to magenta, complementary ramp to green, layering yields gray "); } else if (this->Type == this->WarmShade1) @@ -717,7 +709,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from black to red, of 256 colors, ramp of warm colors with variation in value that's complementary to CoolShade1 "); } else if (this->Type == this->WarmShade2) @@ -730,7 +721,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription( "A scale from black to yellow, through green, of 256 colors, ramp of warm colors with variation in value that's complementary to CoolShade2 "); } @@ -744,7 +734,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from black to green, of 256 colors, ramp of warm colors with variation in value that's complementary to CoolShade3 "); } else if (this->Type == this->CoolShade1) @@ -757,7 +746,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from black to cyan, 256 colors, ramp of cool colors with variation in value that is complementary to WarmShade1 "); } else if (this->Type == this->CoolShade2) @@ -770,7 +758,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription( "A scale from black to blue through purple, 256 colors, ramp of cool colors with variation in value that is complementary to WarmShade2 "); } @@ -784,7 +771,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(0, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from black to magenta, 256 colors, ramp of cool colors with variation in value that is complementary to WarmShade3"); } else if (this->Type == this->WarmTint1) @@ -797,7 +783,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from white to red, 256 colors, ramp of warm colors with variation in saturation that's complementary to CoolTint1"); } else if (this->Type == this->WarmTint2) @@ -810,7 +795,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from white to yellow, 256 colors, ramp of warm colors with variation in saturation that's complementary to CoolTint2"); } else if (this->Type == this->WarmTint3) @@ -823,7 +807,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from white to green, 256 colors, ramp of warm colors with variation in saturation that's complementary to CoolTint3"); } else if (this->Type == this->CoolTint1) @@ -836,7 +819,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from white to cyan, 256 colors, ramp of cool colors with variations in saturation that's complementary to WarmTint1"); } else if (this->Type == this->CoolTint2) @@ -849,7 +831,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from white to blue, 256 colors, ramp of cool colors with variations in saturation that's complementary to WarmTint2"); } else if (this->Type == this->CoolTint3) @@ -862,7 +843,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from white to magenta, 256 colors, ramp of cool colors with variations in saturation that's complementary to WarmTint3"); } else if (this->Type == this->Warm1) @@ -875,7 +855,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from yellow to red, of 256 colors, ramp of warm colors that's complementary to Cool1"); } else if (this->Type == this->Warm2) @@ -888,7 +867,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from green to yellow, 256 colors, ramp of warm colors that's complementary to Cool2"); } else if (this->Type == this->Warm3) @@ -901,7 +879,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from cyan to green, 256 colors, ramp of warm colors that's complementary to Cool3"); } else if (this->Type == this->Cool1) @@ -914,7 +891,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from blue to cyan, 256 colors, ramp of cool colors that's complementary to Warm1"); } else if (this->Type == this->Cool2) @@ -927,7 +903,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from magenta to blue, 256 colors, ramp of cool colors that's complementary to Warm2"); } else if (this->Type == this->Cool3) @@ -940,7 +915,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetAlphaRange(1, 1); // not used this->GetLookupTable()->Build(); - this->SetNamesFromColors(); this->SetDescription("A scale from red to magenta, ramp of cool colors that's complementary to Warm3"); } else if (this->Type == this->Iron) @@ -952,7 +926,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1,1); this->GetLookupTable()->SetRampToLinear(); this->GetLookupTable()->ForceBuild(); - this->SetNamesFromColors(); this->SetDescription("A scale from red to yellow, 157 colors, useful for "); } @@ -964,7 +937,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1,1); this->GetLookupTable()->SetRampToLinear(); this->GetLookupTable()->ForceBuild(); - this->SetNamesFromColors(); this->SetDescription("Goes from red to purple, passing through the colors of the rainbow in between. Useful for a colorful display of a label map"); } @@ -976,7 +948,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1,1); this->GetLookupTable()->SetRampToLinear(); this->GetLookupTable()->ForceBuild(); - this->SetNamesFromColors(); this->SetDescription("A lighter blue scale of 256 values, useful for showing registration results."); } else if (this->Type == this->Desert) @@ -987,7 +958,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1,1); this->GetLookupTable()->SetRampToLinear(); this->GetLookupTable()->ForceBuild(); - this->SetNamesFromColors(); this->SetDescription("Red to yellow/orange scale, 256 colors, useful for "); } @@ -999,7 +969,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1,0); this->GetLookupTable()->SetRampToLinear(); this->GetLookupTable()->ForceBuild(); - this->SetNamesFromColors(); this->SetDescription("A white to black scale, 256 colors, useful to highlight negative versions, or to flip intensities of signal values."); } @@ -1011,7 +980,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1,1); this->GetLookupTable()->SetRampToLinear(); this->GetLookupTable()->ForceBuild(); - this->SetNamesFromColors(); this->SetDescription("A colorful display option, 256 colors going from purple to red"); } @@ -1054,7 +1022,6 @@ void vtkMRMLColorTableNode::SetType(int type) pos->Delete(); neg->Delete(); - this->SetNamesFromColors(); this->SetDescription("A combination of Ocean (0-22) and Desert (23-42), useful for displaying functional MRI volumes (highlights activation)"); } @@ -1068,7 +1035,6 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetValueRange(1, 1); this->GetLookupTable()->SetRampToLinear(); this->GetLookupTable()->ForceBuild(); - this->SetNamesFromColors(); this->SetDescription("A small fMRI positive activation scale going from red to yellow from 0-19, useful for displaying " "functional MRI volumes when don't need the blue of the fMRI scale."); } @@ -1193,7 +1159,7 @@ void vtkMRMLColorTableNode::SetType(int type) this->GetLookupTable()->SetTableValue(i, r, g, b, 1.0); } - this->SetNamesFromColors(); + this->SetDescription( "A random selection of 256 rgb colors, useful to distinguish between a small number of labeled regions (especially outside of the brain)"); } @@ -1246,7 +1212,7 @@ void vtkMRMLColorTableNode::SetNumberOfColors(int n) MRMLNodeModifyBlocker blocker(this); this->GetLookupTable()->SetNumberOfTableValues(n); // Initialize new elements (needed if color table is made larger). - this->ClearColors(numberOfTableValuesBefore, n - 1); + this->RemoveColors(numberOfTableValuesBefore, n - 1); } if (n >= int(this->Properties.size())) { @@ -1270,7 +1236,7 @@ int vtkMRMLColorTableNode::GetNumberOfColors() } //--------------------------------------------------------------------------- -void vtkMRMLColorTableNode::AddColor(const char *name, double r, double g, double b, double a, NameSourceType nameSource/*=NameSourceUnknown*/) +void vtkMRMLColorTableNode::AddColor(const char *name, double r, double g, double b, double a) { if (this->GetType() != this->User && this->GetType() != this->File) @@ -1279,12 +1245,11 @@ void vtkMRMLColorTableNode::AddColor(const char *name, double r, double g, doubl return; } this->LastAddedColor++; - this->SetColor(this->LastAddedColor, name, r, g, b, a, nameSource); + this->SetColor(this->LastAddedColor, name, r, g, b, a); } //--------------------------------------------------------------------------- -int vtkMRMLColorTableNode::SetColor(int entry, const char *name, double r, double g, double b, double a, - NameSourceType nameSource/*=NameSourceUnknown*/) +int vtkMRMLColorTableNode::SetColor(int entry, const char *name, double r, double g, double b, double a) { if (this->GetType() != this->User && this->GetType() != this->File) { @@ -1299,7 +1264,7 @@ int vtkMRMLColorTableNode::SetColor(int entry, const char *name, double r, doubl } this->GetLookupTable()->SetTableValue(entry, r, g, b, a); - if (this->SetColorName(entry, name, nameSource) == 0) + if (this->SetColorName(entry, name) == 0) { vtkWarningMacro("SetColor: error setting color name " << name << " for entry " << entry); return 0; @@ -1331,19 +1296,13 @@ int vtkMRMLColorTableNode::SetColor(int entry, double r, double g, double b, dou } this->GetLookupTable()->SetTableValue(entry, r, g, b, a); - if (this->GetColorNameSource(entry) == NameAutoGeneratedFromColor) - { - this->SetNameFromColor(entry); - } - // trigger a modified event this->Modified(); return 1; } //--------------------------------------------------------------------------- -int vtkMRMLColorTableNode::SetColors(int firstEntry, int lastEntry, const char *name, double r, double g, double b, double a, - NameSourceType nameSource/*=NameSourceUnknown*/) +int vtkMRMLColorTableNode::SetColors(int firstEntry, int lastEntry, const char *name, double r, double g, double b, double a) { if (this->GetType() != this->User && this->GetType() != this->File) { @@ -1387,7 +1346,6 @@ int vtkMRMLColorTableNode::SetColors(int firstEntry, int lastEntry, const char * *(rgba++) = static_cast(b * 255.0 + 0.5); *(rgba++) = static_cast(a * 255.0 + 0.5); this->Properties[indx].Name = nameStr; - this->Properties[indx].NameSource = nameSource; } lut->BuildSpecialColors(); lut->Modified(); @@ -1397,11 +1355,11 @@ int vtkMRMLColorTableNode::SetColors(int firstEntry, int lastEntry, const char * } //--------------------------------------------------------------------------- -bool vtkMRMLColorTableNode::ClearColors(int firstEntry, int lastEntry) +bool vtkMRMLColorTableNode::RemoveColors(int firstEntry, int lastEntry) { if (this->GetType() != this->User && this->GetType() != this->File) { - vtkErrorMacro("vtkMRMLColorTableNode::ClearColors: Cannot set a color if not a user defined color table, reset the type first to User or File"); + vtkErrorMacro("vtkMRMLColorTableNode::RemoveColors: Cannot set a color if not a user defined color table, reset the type first to User or File"); return false; } if (firstEntry > lastEntry) @@ -1416,13 +1374,13 @@ bool vtkMRMLColorTableNode::ClearColors(int firstEntry, int lastEntry) } if (firstEntry < 0 || firstEntry >= numberOfValues) { - vtkErrorMacro("vtkMRMLColorTableNode::ClearColors: requested first entry " + vtkErrorMacro("vtkMRMLColorTableNode::RemoveColors: requested first entry " << firstEntry << " is out of table range: 0 - " << numberOfValues << ", call SetNumberOfColors"); return false; } if (lastEntry < 0 || lastEntry >= numberOfValues) { - vtkErrorMacro("vtkMRMLColorTableNode::ClearColors: requested last entry " + vtkErrorMacro("vtkMRMLColorTableNode::RemoveColors: requested last entry " << lastEntry << " is out of table range: 0 - " << numberOfValues << ", call SetNumberOfColors"); return false; } @@ -1462,11 +1420,11 @@ int vtkMRMLColorTableNode::SetColor(int entry, double r, double g, double b) } //--------------------------------------------------------------------------- -bool vtkMRMLColorTableNode::ClearColor(int entry) +bool vtkMRMLColorTableNode::RemoveColor(int entry) { if (entry < 0 || entry >= this->GetLookupTable()->GetNumberOfTableValues()) { - vtkErrorMacro("vtkMRMLColorTableNode::ClearColor: requested entry " << entry << " is out of table range: 0 - " + vtkErrorMacro("vtkMRMLColorTableNode::RemoveColor: requested entry " << entry << " is out of table range: 0 - " << this->GetLookupTable()->GetNumberOfTableValues() - 1); return false; } @@ -1475,6 +1433,7 @@ bool vtkMRMLColorTableNode::ClearColor(int entry) if (entry <= this->Properties.size()) { this->Properties[entry].Clear(); + this->Modified(); } return true; } diff --git a/Libs/MRML/Core/vtkMRMLColorTableNode.h b/Libs/MRML/Core/vtkMRMLColorTableNode.h index b03a2577b9c..8309bc989cb 100644 --- a/Libs/MRML/Core/vtkMRMLColorTableNode.h +++ b/Libs/MRML/Core/vtkMRMLColorTableNode.h @@ -206,23 +206,23 @@ class VTK_MRML_EXPORT vtkMRMLColorTableNode : public vtkMRMLColorNode /// /// Add a color to the User color table, at the end - void AddColor(const char* name, double r, double g, double b, double a = 1.0, NameSourceType nameSource = NameSourceUnknown); + void AddColor(const char* name, double r, double g, double b, double a = 1.0); /// /// Set a color into the User color table. Return 1 on success, 0 on failure. - int SetColor(int entry, const char* name, double r, double g, double b, double a = 1.0, NameSourceType nameSource = NameSourceUnknown); + int SetColor(int entry, const char* name, double r, double g, double b, double a = 1.0); /// Undefine color entry. /// Returns true on success. - bool ClearColor(int entry); + bool RemoveColor(int entry); /// Set many entries to the same name and color in one batch (with one ModifiedEvent). /// This is much more efficient than setting many color entries one by one using SetColor(). - int SetColors(int firstEntry, int lastEntry, const char* name, double r, double g, double b, double a = 1.0, NameSourceType nameSource=NameSourceUnknown); + int SetColors(int firstEntry, int lastEntry, const char* name, double r, double g, double b, double a = 1.0); /// Undefines entries in the specified range. /// Returns true on success, false on failure. - bool ClearColors(int firstEntry, int lastEntry); + bool RemoveColors(int firstEntry, int lastEntry); /// Set a color into the User color table. Return 1 on success, 0 on failure. int SetColor(int entry, double r, double g, double b, double a); diff --git a/Libs/MRML/Core/vtkMRMLColorTableStorageNode.cxx b/Libs/MRML/Core/vtkMRMLColorTableStorageNode.cxx index ff586afcdde..0c0b7128eed 100644 --- a/Libs/MRML/Core/vtkMRMLColorTableStorageNode.cxx +++ b/Libs/MRML/Core/vtkMRMLColorTableStorageNode.cxx @@ -304,7 +304,7 @@ int vtkMRMLColorTableStorageNode::ReadCsvFile(std::string fullFileName, vtkMRMLC colorNode->GetLookupTable()->SetTableRange(0, maxLabelValue); } // init the table to default, just in case we're missing values - colorNode->ClearColors(0, maxLabelValue); + colorNode->RemoveColors(0, maxLabelValue); // Define helper function for populating terminology entry IDs auto GetIndexInEntryForIdType = [](std::string idType) @@ -497,7 +497,7 @@ int vtkMRMLColorTableStorageNode::ReadCtblFile(std::string fullFileName, vtkMRML colorNode->GetLookupTable()->SetTableRange(0, maxID); } // init the table to default, just in case we're missing values - colorNode->ClearColors(0, maxID); + colorNode->RemoveColors(0, maxID); // do a little sanity check, if never get an rgb bigger than 1.0, report // it as a possibly miswritten file @@ -566,7 +566,7 @@ int vtkMRMLColorTableStorageNode::ReadCtblFile(std::string fullFileName, vtkMRML } // Space in name is replaced by underscores for storage, we restore the original name now std::replace(name.begin(), name.end(), '_', ' '); - colorNode->SetColorName(id, name.c_str(), vtkMRMLColorNode::NameSourceUnknown); + colorNode->SetColorName(id, name.c_str()); } if (lines.size() > 0 && !biggerThanOne) { diff --git a/Libs/MRML/Core/vtkMRMLProceduralColorNode.cxx b/Libs/MRML/Core/vtkMRMLProceduralColorNode.cxx index b1fa27447f4..c93c6b6f3e6 100644 --- a/Libs/MRML/Core/vtkMRMLProceduralColorNode.cxx +++ b/Libs/MRML/Core/vtkMRMLProceduralColorNode.cxx @@ -201,7 +201,7 @@ bool vtkMRMLProceduralColorNode::SetNameFromColor(int index) ss << color[1]; ss << " B="; ss << color[2]; - if (this->SetColorName(index, ss.str().c_str(), NameAutoGeneratedFromColor) == 0) + if (this->SetColorName(index, ss.str().c_str()) == 0) { vtkErrorMacro("SetNamesFromColors: error setting name " << ss.str().c_str() << " for color index " << index); return false; diff --git a/Libs/MRML/Core/vtkMRMLScene.cxx b/Libs/MRML/Core/vtkMRMLScene.cxx index 003291a79a4..38ac8189895 100644 --- a/Libs/MRML/Core/vtkMRMLScene.cxx +++ b/Libs/MRML/Core/vtkMRMLScene.cxx @@ -3871,7 +3871,8 @@ std::string vtkMRMLScene::GetTemporaryBundleDirectory() std::uniform_int_distribution distribution(0, numberOfCharacters); for (int i = 0; i < 5; i++) { - ss << validCharacters[distribution(this->RandomGenerator)]; + // % added just in case the random generator creates numbers outside of the distribution (it seemed to happen) + ss << validCharacters[distribution(this->RandomGenerator) % numberOfCharacters]; } return ss.str(); } diff --git a/Libs/MRML/Logic/Testing/Cxx/vtkMRMLColorLogicTest1.cxx b/Libs/MRML/Logic/Testing/Cxx/vtkMRMLColorLogicTest1.cxx index 0b88b0e957f..2dd4a89ff0d 100644 --- a/Libs/MRML/Logic/Testing/Cxx/vtkMRMLColorLogicTest1.cxx +++ b/Libs/MRML/Logic/Testing/Cxx/vtkMRMLColorLogicTest1.cxx @@ -204,7 +204,7 @@ bool TestCopy() originalNode->SetNumberOfColors(6); originalNode->GetLookupTable()->SetTableRange(0, 5); // Use NoName as color name to not list the "background" color in the color legend. - originalNode->ClearColor(0); + originalNode->RemoveColor(0); originalNode->SetColor(1, "one", 0.5, 1.0, 0.0, 0.1); originalNode->SetColor(2, "two", 0.5, 0.5, 0.0, 0.3); originalNode->SetColor(3, "three", 0.33, 0.0, 0.5, 0.5); diff --git a/Libs/MRML/Logic/vtkMRMLColorLogic.cxx b/Libs/MRML/Logic/vtkMRMLColorLogic.cxx index 6ef09014f1d..f56b856a0de 100644 --- a/Libs/MRML/Logic/vtkMRMLColorLogic.cxx +++ b/Libs/MRML/Logic/vtkMRMLColorLogic.cxx @@ -507,7 +507,6 @@ vtkMRMLProceduralColorNode* vtkMRMLColorLogic::CreateRandomNode() } func->BuildFunctionFromTable(VTK_INT_MIN, VTK_INT_MAX, dimension, table); func->Build(); - procNode->SetNamesFromColors(); return procNode; } @@ -528,8 +527,6 @@ vtkMRMLProceduralColorNode* vtkMRMLColorLogic::CreateRedGreenBlueNode() func->AddRGBPoint(0.0, 0.0, 1.0, 0.0); func->AddRGBPoint(6.0, 0.0, 0.0, 1.0); - procNode->SetNamesFromColors(); - return procNode; } @@ -801,7 +798,8 @@ void vtkMRMLColorLogic::AddDGEMRICNode(int type) else { vtkWarningMacro("AddDGEMRICNode failed with type " << type); - }} + } +} //---------------------------------------------------------------------------------------- void vtkMRMLColorLogic::AddDefaultFileNode(int i) diff --git a/Libs/MRML/Widgets/qMRMLColorModel.cxx b/Libs/MRML/Widgets/qMRMLColorModel.cxx index 05d8b05fb46..c2519879934 100644 --- a/Libs/MRML/Widgets/qMRMLColorModel.cxx +++ b/Libs/MRML/Widgets/qMRMLColorModel.cxx @@ -380,7 +380,7 @@ void qMRMLColorModel::updateItemFromColor(QStandardItem* item, int color, int co } item->setData(color, qMRMLItemDelegate::ColorEntryRole); - QString colorName = d->MRMLColorNode->GetColorName(color); + QString colorName = this->nameFromColor(color); if (column == d->ColorColumn) { @@ -510,43 +510,116 @@ QVariant qMRMLColorModel::headerData(int section, Qt::Orientation orientation, i } //------------------------------------------------------------------------------ -QString qMRMLColorModel::terminologyTextForColor(vtkMRMLColorNode* colorNode, int colorIndex) +QString qMRMLColorModel::terminologyTextForColor(vtkMRMLColorNode* colorNode, int colorIndex, bool simplified/*=false*/) { - QString text; if (colorNode == nullptr || colorIndex >= colorNode->GetNumberOfColors()) { - return text; + return QString(); } - if ( colorNode->GetTerminologyCategory(colorIndex) == nullptr || colorNode->GetTerminologyCategory(colorIndex)->GetCodeMeaning() == nullptr - || colorNode->GetTerminologyType(colorIndex) == nullptr || colorNode->GetTerminologyType(colorIndex)->GetCodeMeaning() == nullptr) + // Get type (with modifier, if any) + QString type; + if (colorNode->GetTerminologyType(colorIndex) != nullptr + && colorNode->GetTerminologyType(colorIndex)->GetCodeMeaning() != nullptr) { - text.append(vtkMRMLTr("qMRMLColorModel", "(none)").c_str()); - return text; + type = colorNode->GetTerminologyType(colorIndex)->GetCodeMeaning(); + QString typeModifier; + if (colorNode->GetTerminologyTypeModifier(colorIndex) != nullptr + && colorNode->GetTerminologyTypeModifier(colorIndex)->GetCodeMeaning() != nullptr) + { + typeModifier = colorNode->GetTerminologyTypeModifier(colorIndex)->GetCodeMeaning(); + } + if (!typeModifier.isEmpty()) + { + if (simplified) + { + //: For formatting of terminology entry with a modifier in simplified mode. %1 is structure name (e.g., "Kidney"), %2 is modifier (e.g., "Left") + type = tr("%2 %1").arg(type).arg(typeModifier); + } + else + { + //: For formatting of terminology entry with a modifier. %1 is structure name (e.g., "Kidney"), %2 is modifier (e.g., "Left") + type = tr("%1, %2").arg(type).arg(typeModifier); + } + } } - // Add category and type - text.append(QString("%1: %2").arg( - colorNode->GetTerminologyCategory(colorIndex)->GetCodeMeaning()).arg(colorNode->GetTerminologyType(colorIndex)->GetCodeMeaning())); - // Add type modifier if any - if ( colorNode->GetTerminologyTypeModifier(colorIndex) != nullptr - && colorNode->GetTerminologyTypeModifier(colorIndex)->GetCodeMeaning() != nullptr) - { - text.append(QString(", %1 ").arg(colorNode->GetTerminologyTypeModifier(colorIndex)->GetCodeMeaning())); - } - // Add region if any + // Get region (if any; with modifier, if any) + QString region; if ( colorNode->GetTerminologyRegion(colorIndex) != nullptr && colorNode->GetTerminologyRegion(colorIndex)->GetCodeMeaning() != nullptr) { - text.append(vtkMRMLTr("qMRMLColorModel", "in").c_str()); - text.append(QString(" %1").arg(colorNode->GetTerminologyRegion(colorIndex)->GetCodeMeaning())); + region = colorNode->GetTerminologyRegion(colorIndex)->GetCodeMeaning(); + QString regionModifier; + if (colorNode->GetTerminologyRegionModifier(colorIndex) != nullptr + && colorNode->GetTerminologyRegionModifier(colorIndex)->GetCodeMeaning() != nullptr) + { + regionModifier = colorNode->GetTerminologyRegionModifier(colorIndex)->GetCodeMeaning(); + } + if (!regionModifier.isEmpty()) + { + if (simplified) + { + //: For formatting of terminology entry name in simplified mode. %1 is region name (e.g., "Kidney"), %2 is region modifier (e.g., "Left") + region = tr("%2 %1").arg(type).arg(region).arg(regionModifier); + } + else + { + //: For formatting of terminology entry name. %1 is region name (e.g., "Kidney"), %2 is region modifier (e.g., "Left") + region = tr("%1, %2").arg(type).arg(region).arg(regionModifier); + } + } } - // Add region modifier if any - if ( colorNode->GetTerminologyRegionModifier(colorIndex) != nullptr - && colorNode->GetTerminologyRegionModifier(colorIndex)->GetCodeMeaning() != nullptr) + + QString typeInRegion; + if (!type.isEmpty()) + { + if (!region.isEmpty()) + { + //: For formatting of terminology entry name. %1 is type name (e.g., "Mass"), %2 is region name (e.g., "Liver"). + typeInRegion = tr("%1 in %2").arg(type).arg(region); + } + else + { + typeInRegion = type; + } + } + else + { + if (!region.isEmpty()) + { + //: For formatting of terminology entry name. %1 is region name (e.g., "Liver"). + typeInRegion = tr("Unknown in %1").arg(type).arg(region); + } + } + + QString typeInRegionWithCategory; + QString category; + // Skip category in simplified mode + if (!simplified) + { + if (colorNode->GetTerminologyCategory(colorIndex) != nullptr + && colorNode->GetTerminologyCategory(colorIndex)->GetCodeMeaning() != nullptr) + { + category = colorNode->GetTerminologyCategory(colorIndex)->GetCodeMeaning(); + } + } + if (!category.isEmpty()) + { + if (!typeInRegion.isEmpty()) + { + //: For formatting of terminology entry name. %1 is category name (e.g., "Morphologically Altered Structure"), %2 is the type in region ("Mass in Liver") + typeInRegionWithCategory = tr("%1: %2").arg(category).arg(typeInRegion); + } + else + { + typeInRegionWithCategory = category; + } + } + else { - text.append(QString(", %1").arg(colorNode->GetTerminologyRegionModifier(colorIndex)->GetCodeMeaning())); + typeInRegionWithCategory = typeInRegion; } - return text; + return typeInRegionWithCategory; } diff --git a/Libs/MRML/Widgets/qMRMLColorModel.h b/Libs/MRML/Widgets/qMRMLColorModel.h index 6bd70863988..cf951d41d57 100644 --- a/Libs/MRML/Widgets/qMRMLColorModel.h +++ b/Libs/MRML/Widgets/qMRMLColorModel.h @@ -125,7 +125,7 @@ class QMRML_WIDGETS_EXPORT qMRMLColorModel : public QStandardItemModel QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; /// Assemble human readable text in format ": , in , " from color in color node. - static QString terminologyTextForColor(vtkMRMLColorNode* colorNode, int colorIndex); + static QString terminologyTextForColor(vtkMRMLColorNode* colorNode, int colorIndex, bool simplified=false); protected slots: void onMRMLColorNodeModified(vtkObject* node); diff --git a/Modules/Loadable/Colors/Widgets/Resources/UI/qSlicerTerminologyEditorWidget.ui b/Modules/Loadable/Colors/Widgets/Resources/UI/qSlicerTerminologyEditorWidget.ui index 880d7c9d9ab..3e06af4b420 100644 --- a/Modules/Loadable/Colors/Widgets/Resources/UI/qSlicerTerminologyEditorWidget.ui +++ b/Modules/Loadable/Colors/Widgets/Resources/UI/qSlicerTerminologyEditorWidget.ui @@ -236,6 +236,24 @@ + + categoryCodeMeaningLineEdit + categoryCodeValueLineEdit + categoryCSDLineEdit + typeCodeMeaningLineEdit + typeCodeValueLineEdit + typeCSDLineEdit + typeModifierCodeMeaningLineEdit + typeModifierCodeValueLineEdit + typeModifierCSDLineEdit + regionCodeMeaningLineEdit + regionCodeValueLineEdit + regionCSDLineEdit + regionModifierCodeMeaningLineEdit + regionModifierCodeValueLineEdit + regionModifierCSDLineEdit + selectFromTerminologyButton + diff --git a/Modules/Loadable/Colors/Widgets/qSlicerColorTableTerminologyDelegate.cxx b/Modules/Loadable/Colors/Widgets/qSlicerColorTableTerminologyDelegate.cxx index aefd920f61f..ba49ccc592a 100644 --- a/Modules/Loadable/Colors/Widgets/qSlicerColorTableTerminologyDelegate.cxx +++ b/Modules/Loadable/Colors/Widgets/qSlicerColorTableTerminologyDelegate.cxx @@ -119,9 +119,7 @@ void qSlicerColorTableTerminologyDelegate::setEditorData(QWidget *editor, const } // Set terminology information to the button creating the editor dialog - //--------------------------------------------------------------------------- - bool colorNameAutoGenerated = (colorNode->GetColorNameSource(colorIndex) != vtkMRMLColorTableNode::NameSetManually); - + bool colorNameAutoGenerated = false; // auto-generated color names are not stored in the table qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle terminologyInfo( terminologyEntry, colorNode->GetColorName(colorIndex), colorNameAutoGenerated, QColor(), false, QColor() ); @@ -161,9 +159,7 @@ void qSlicerColorTableTerminologyDelegate::setModelData(QWidget* editor, QAbstra vtkMRMLColorTableNode* colorTableNode = vtkMRMLColorTableNode::SafeDownCast(colorNode); if (colorTableNode != nullptr) { - vtkMRMLColorTableNode::NameSourceType nameSource = terminologyInfo.NameAutoGenerated ? - vtkMRMLColorTableNode::NameAutoGeneratedFromTerminology : vtkMRMLColorTableNode::NameSetManually; - colorTableNode->SetColorName(colorIndex, terminologyInfo.Name.toUtf8().constData(), nameSource); + colorTableNode->SetColorName(colorIndex, terminologyInfo.Name.toUtf8().constData()); colorTableNode->SetColor(colorIndex, terminologyInfo.Color.redF(), terminologyInfo.Color.greenF(), terminologyInfo.Color.blueF(), terminologyInfo.Color.alphaF()); } diff --git a/Modules/Loadable/Colors/Widgets/qSlicerTerminologyEditorWidget.cxx b/Modules/Loadable/Colors/Widgets/qSlicerTerminologyEditorWidget.cxx index ac02eef01e7..b3e3be2405c 100644 --- a/Modules/Loadable/Colors/Widgets/qSlicerTerminologyEditorWidget.cxx +++ b/Modules/Loadable/Colors/Widgets/qSlicerTerminologyEditorWidget.cxx @@ -36,6 +36,7 @@ class qSlicerTerminologyEditorWidgetPrivate : public Ui_qSlicerTerminologyEditor void init(); void updateGUIFromTerminologyInfo(); + void updateTerminologyFromGUI(); qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle TerminologyInfo; }; @@ -82,6 +83,32 @@ void qSlicerTerminologyEditorWidgetPrivate::updateGUIFromTerminologyInfo() this->regionModifierCSDLineEdit->setText(TerminologyInfo.GetTerminologyEntry()->GetRegionModifierObject()->GetCodingSchemeDesignator()); } +void qSlicerTerminologyEditorWidgetPrivate::updateTerminologyFromGUI() +{ + Q_Q(qSlicerTerminologyEditorWidget); + + // Update terminology info from the GUI + TerminologyInfo.GetTerminologyEntry()->GetCategoryObject()->SetCodeMeaning(this->categoryCodeMeaningLineEdit->text().toStdString().c_str()); + TerminologyInfo.GetTerminologyEntry()->GetCategoryObject()->SetCodeValue(this->categoryCodeValueLineEdit->text().toStdString().c_str()); + TerminologyInfo.GetTerminologyEntry()->GetCategoryObject()->SetCodingSchemeDesignator(this->categoryCSDLineEdit->text().toStdString().c_str()); + + TerminologyInfo.GetTerminologyEntry()->GetTypeObject()->SetCodeMeaning(this->typeCodeMeaningLineEdit->text().toStdString().c_str()); + TerminologyInfo.GetTerminologyEntry()->GetTypeObject()->SetCodeValue(this->typeCodeValueLineEdit->text().toStdString().c_str()); + TerminologyInfo.GetTerminologyEntry()->GetTypeObject()->SetCodingSchemeDesignator(this->typeCSDLineEdit->text().toStdString().c_str()); + + TerminologyInfo.GetTerminologyEntry()->GetTypeModifierObject()->SetCodeMeaning(this->typeModifierCodeMeaningLineEdit->text().toStdString().c_str()); + TerminologyInfo.GetTerminologyEntry()->GetTypeModifierObject()->SetCodeValue(this->typeModifierCodeValueLineEdit->text().toStdString().c_str()); + TerminologyInfo.GetTerminologyEntry()->GetTypeModifierObject()->SetCodingSchemeDesignator(this->typeModifierCSDLineEdit->text().toStdString().c_str()); + + TerminologyInfo.GetTerminologyEntry()->GetRegionObject()->SetCodeMeaning(this->regionCodeMeaningLineEdit->text().toStdString().c_str()); + TerminologyInfo.GetTerminologyEntry()->GetRegionObject()->SetCodeValue(this->regionCodeValueLineEdit->text().toStdString().c_str()); + TerminologyInfo.GetTerminologyEntry()->GetRegionObject()->SetCodingSchemeDesignator(this->regionCSDLineEdit->text().toStdString().c_str()); + + TerminologyInfo.GetTerminologyEntry()->GetRegionModifierObject()->SetCodeMeaning(this->regionModifierCodeMeaningLineEdit->text().toStdString().c_str()); + TerminologyInfo.GetTerminologyEntry()->GetRegionModifierObject()->SetCodeValue(this->regionModifierCodeValueLineEdit->text().toStdString().c_str()); + TerminologyInfo.GetTerminologyEntry()->GetRegionModifierObject()->SetCodingSchemeDesignator(this->regionModifierCSDLineEdit->text().toStdString().c_str()); +} + //------------------------------------------------------------------------------ qSlicerTerminologyEditorWidget::qSlicerTerminologyEditorWidget(QWidget *_parent) : QWidget(_parent) @@ -98,6 +125,7 @@ qSlicerTerminologyEditorWidget::~qSlicerTerminologyEditorWidget() = default; void qSlicerTerminologyEditorWidget::terminologyInfo(qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle &terminologyInfo) { Q_D(qSlicerTerminologyEditorWidget); + d->updateTerminologyFromGUI(); terminologyInfo = d->TerminologyInfo; } @@ -113,7 +141,7 @@ void qSlicerTerminologyEditorWidget::setTerminologyInfo(qSlicerTerminologyNaviga void qSlicerTerminologyEditorWidget::onSelectFromTerminology() { Q_D(qSlicerTerminologyEditorWidget); - + d->updateTerminologyFromGUI(); qSlicerTerminologySelectorDialog::getTerminology(d->TerminologyInfo, this); d->updateGUIFromTerminologyInfo(); } diff --git a/Modules/Loadable/Colors/qSlicerColorsModuleWidget.cxx b/Modules/Loadable/Colors/qSlicerColorsModuleWidget.cxx index 16aa786639c..bfb89bf726d 100644 --- a/Modules/Loadable/Colors/qSlicerColorsModuleWidget.cxx +++ b/Modules/Loadable/Colors/qSlicerColorsModuleWidget.cxx @@ -195,6 +195,8 @@ void qSlicerColorsModuleWidget::onMRMLColorNodeChanged(vtkMRMLNode* newColorNode vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast(newColorNode); if (!colorNode) { + d->AddNewColorButton->setEnabled(false); + d->RemoveCurrentColorButton->setEnabled(false); d->NumberOfColorsSpinBox->setEnabled(false); d->NumberOfColorsSpinBox->setValue(0); d->LUTRangeWidget->setEnabled(false); @@ -218,15 +220,19 @@ void qSlicerColorsModuleWidget::onMRMLColorNodeChanged(vtkMRMLNode* newColorNode d->ColorTableFrame->show(); d->EditColorsCollapsibleButton->setText(tr("Discrete table")); + bool editable = (colorNode->GetType() == vtkMRMLColorTableNode::User); + // number of colors - d->NumberOfColorsSpinBox->setEnabled( - colorNode->GetType() == vtkMRMLColorTableNode::User); + d->NumberOfColorsSpinBox->setEnabled(editable); d->NumberOfColorsSpinBox->setValue(colorNode->GetNumberOfColors()); Q_ASSERT(d->NumberOfColorsSpinBox->value() == colorNode->GetNumberOfColors()); + d->AddNewColorButton->setEnabled(editable); + d->RemoveCurrentColorButton->setEnabled(editable); + // set the range and the input for the color widget depending on if it's a freesurfer node or a color table node double *range = nullptr; - d->LUTRangeWidget->setEnabled(colorNode->GetType() == vtkMRMLColorTableNode::User); + d->LUTRangeWidget->setEnabled(editable); if (colorTableNode && colorTableNode->GetLookupTable()) { range = colorTableNode->GetLookupTable()->GetRange(); @@ -398,7 +404,7 @@ void qSlicerColorsModuleWidget::addNewColorInCurrentNode() // Add a color to the current (User type) color table, at the end int newNumber = currentNode->GetNumberOfColors() + 1; currentNode->SetNumberOfColors(newNumber); - currentNode->SetColor(newNumber - 1, "", 0.5, 0.5, 0.5, 1.0, vtkMRMLColorNode::NameSourceUnknown); + currentNode->SetColor(newNumber - 1, "", 0.5, 0.5, 0.5, 1.0); // Update spinbox on GUI as well QSignalBlocker blocker(d->NumberOfColorsSpinBox); d->NumberOfColorsSpinBox->setValue(newNumber); @@ -410,8 +416,16 @@ void qSlicerColorsModuleWidget::removeCurrentColorEntry() Q_D(qSlicerColorsModuleWidget); vtkMRMLColorTableNode* currentNode = vtkMRMLColorTableNode::SafeDownCast(d->ColorTableComboBox->currentNode()); Q_ASSERT(currentNode); - //TODO: Color entry cannot currently be deleted. It can be set to "none", but the hide empty colors function - // does not work currently. Unfortunately the color table node does not support removing simgle entry either. + int colorIndex = -1; + if (d->ColorTableFrame->isVisible()) + { + // TODO: this does not provide correct index when "Hide empty colors" is enabled + colorIndex = d->ColorView->currentIndex().row(); + } + if (colorIndex >= 0) + { + currentNode->RemoveColor(colorIndex); + } } //----------------------------------------------------------- diff --git a/Modules/Loadable/Segmentations/Logic/vtkSlicerSegmentationsModuleLogic.cxx b/Modules/Loadable/Segmentations/Logic/vtkSlicerSegmentationsModuleLogic.cxx index b80b30dcbe0..c370cfc2979 100644 --- a/Modules/Loadable/Segmentations/Logic/vtkSlicerSegmentationsModuleLogic.cxx +++ b/Modules/Loadable/Segmentations/Logic/vtkSlicerSegmentationsModuleLogic.cxx @@ -1130,7 +1130,7 @@ bool vtkSlicerSegmentationsModuleLogic::ExportSegmentsToColorTableNode(vtkMRMLSe colorTableNode->GetLookupTable()->SetRange(0, numberOfColors - 1); colorTableNode->GetLookupTable()->SetNumberOfTableValues(numberOfColors); // Remove "background" from the color legend. - colorTableNode->SetColorDefined(0, false); + colorTableNode->RemoveColor(0); short colorIndex = 0; for (std::vector::iterator segmentIt = exportedSegmentIDs.begin(); segmentIt != exportedSegmentIDs.end(); ++segmentIt, ++colorIndex) @@ -1258,7 +1258,7 @@ bool vtkSlicerSegmentationsModuleLogic::ExportSegmentsToLabelmapNode(vtkMRMLSegm // Copy segment colors to color table node vtkMRMLColorTableNode* colorTableNode = vtkMRMLColorTableNode::SafeDownCast( labelmapNode->GetDisplayNode()->GetColorNode() ); // Always valid, as it was created just above if was missing - vtkSlicerSegmentationsModuleLogic::ExportSegmentsToColorTableNode(segmentationNode, segmentIDs, colorTableNode); + vtkSlicerSegmentationsModuleLogic::ExportSegmentsToColorTableNode(segmentationNode, segmentIDs, colorTableNode, labelValues); // Move exported labelmap node under same parent as segmentation if they are in the same scene vtkMRMLSubjectHierarchyNode* shNode = vtkMRMLSubjectHierarchyNode::GetSubjectHierarchyNode(segmentationNode->GetScene()); diff --git a/Modules/Loadable/Terminologies/Logic/vtkSlicerTerminologiesModuleLogic.cxx b/Modules/Loadable/Terminologies/Logic/vtkSlicerTerminologiesModuleLogic.cxx index 1197d8b8cfa..3c298885f6c 100644 --- a/Modules/Loadable/Terminologies/Logic/vtkSlicerTerminologiesModuleLogic.cxx +++ b/Modules/Loadable/Terminologies/Logic/vtkSlicerTerminologiesModuleLogic.cxx @@ -2190,6 +2190,7 @@ bool vtkSlicerTerminologiesModuleLogic::UpdateEntryFromLoadedTerminologies(vtkSl CodeIdentifier categoryId = GetCodeIdentifierFromCodedEntry(entry->GetCategoryObject()); CodeIdentifier typeId = GetCodeIdentifierFromCodedEntry(entry->GetTypeObject()); + CodeIdentifier typeModifierId = GetCodeIdentifierFromCodedEntry(entry->GetTypeModifierObject()); if (categoryId.IsValid() && typeId.IsValid()) { // Create list of preferred terminology names: the list starts with the entry's terminologyName @@ -2217,19 +2218,29 @@ bool vtkSlicerTerminologiesModuleLogic::UpdateEntryFromLoadedTerminologies(vtkSl { continue; } - entry->SetTerminologyContextName(terminologyName.c_str()); - entry->GetCategoryObject()->Copy(categoryObject); - entry->GetTypeObject()->Copy(typeObject); - CodeIdentifier typeModifierId = GetCodeIdentifierFromCodedEntry(entry->GetTypeModifierObject()); - if (typeModifierId.IsValid()) + bool found = false; + if (!typeModifierId.IsValid()) + { + // Type without a modifier + found = true; + } + else { // Type with a modifier vtkNew typeModifierObject; if (this->GetTypeModifierInTerminologyType(terminologyName, categoryId, typeId, typeModifierId, typeModifierObject)) { + found = true; entry->GetTypeModifierObject()->Copy(typeModifierObject); } } + if (found) + { + entry->SetTerminologyContextName(terminologyName.c_str()); + entry->GetCategoryObject()->Copy(categoryObject); + entry->GetTypeObject()->Copy(typeObject); + break; + } } } @@ -2258,18 +2269,29 @@ bool vtkSlicerTerminologiesModuleLogic::UpdateEntryFromLoadedTerminologies(vtkSl { continue; } - entry->SetRegionContextName(regionContextName.c_str()); - entry->GetRegionObject()->Copy(regionObject); + bool found = false; CodeIdentifier regionModifierId = GetCodeIdentifierFromCodedEntry(entry->GetRegionModifierObject()); - if (regionModifierId.IsValid()) + if (!regionModifierId.IsValid()) + { + // Region without a modifier + found = true; + } + else { // Region with a modifier vtkNew regionModifierObject; if (this->GetRegionModifierInRegion(regionContextName, regionId, regionModifierId, regionModifierObject)) { + found = true; entry->GetRegionModifierObject()->Copy(regionModifierObject); } } + if (found) + { + entry->SetRegionContextName(regionContextName.c_str()); + entry->GetRegionObject()->Copy(regionObject); + break; + } } } @@ -2810,3 +2832,125 @@ int vtkSlicerTerminologiesModuleLogic::GetColorIndexByTerminology(vtkMRMLColorNo return -1; // Not found } + +//--------------------------------------------------------------------------- +std::vector vtkSlicerTerminologiesModuleLogic::GetCompatibleColorNodeIDs() +{ + std::vector compatibleColorNodeIDs; + vtkMRMLScene* scene = this->GetMRMLScene(); + if (!scene) + { + vtkErrorMacro("GetCompatibleColorNodeIDs: Invalid MRML scene"); + return compatibleColorNodeIDs; + } + std::vector colorNodes; + scene->GetNodesByClass("vtkMRMLColorNode", colorNodes); + for (vtkMRMLNode* node : colorNodes) + { + vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast(node); + if (colorNode && colorNode->GetContainsTerminology()) + { + compatibleColorNodeIDs.push_back(colorNode->GetID()); + } + } + return compatibleColorNodeIDs; +} + +//--------------------------------------------------------------------------- +vtkMRMLColorNode* vtkSlicerTerminologiesModuleLogic::GetFirstCompatibleColorNodeByName(std::string name) +{ + std::vector compatibleColorNodeIDs; + vtkMRMLScene* scene = this->GetMRMLScene(); + if (!scene) + { + vtkErrorMacro("GetFirstCompatibleColorNodeByName: Invalid MRML scene"); + return nullptr; + } + std::vector colorNodes; + scene->GetNodesByClass("vtkMRMLColorNode", colorNodes); + for (vtkMRMLNode* node : colorNodes) + { + vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast(node); + if (colorNode && colorNode->GetContainsTerminology()) + { + if (colorNode->GetName()) + { + if (name == colorNode->GetName()) + { + // found color node that has matching name + return colorNode; + } + } + } + } + return nullptr; +} + +//--------------------------------------------------------------------------- +std::vector vtkSlicerTerminologiesModuleLogic::FindColorNodes( + std::string categoryCodingSchemeDesignator, std::string categoryCodeValue, + std::string typeCodingSchemeDesignator, std::string typeCodeValue, + std::string typeModifierCodingSchemeDesignator, std::string typeModifierCodeValue, + std::string regionCodingSchemeDesignator, std::string regionCodeValue, + std::string regionModifierCodingSchemeDesignator, std::string regionModifierCodeValue, + std::vector preferredColorNodeNames, + vtkIntArray* foundColorIndices/*=nullptr*/) +{ + std::vector foundColorNodeIDs; + // Find candidate color nodes + vtkMRMLScene* scene = this->GetMRMLScene(); + if (!scene) + { + vtkErrorMacro("FindColorTableNodes: Invalid MRML scene"); + return foundColorNodeIDs; + } + std::vector compatibleColorNodeIDs; + if (preferredColorNodeNames.empty()) + { + compatibleColorNodeIDs = vtkSlicerTerminologiesModuleLogic::GetCompatibleColorNodeIDs(); + } + else + { + for (const std::string& preferredColorNodeName : preferredColorNodeNames) + { + vtkSmartPointer preferredColorNodeCandidates = vtkSmartPointer::Take( + scene->GetNodesByClassByName("vtkMRMLColorNode", preferredColorNodeName.c_str())); + for (int i = 0; i < preferredColorNodeCandidates->GetNumberOfItems(); ++i) + { + vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast(preferredColorNodeCandidates->GetItemAsObject(i)); + if (colorNode && colorNode->GetContainsTerminology()) + { + compatibleColorNodeIDs.push_back(colorNode->GetID()); + } + } + } + } + // Check if we can find the item in the table + std::string terminologyStr = vtkSlicerTerminologiesModuleLogic::SerializeTerminologyEntry( + "", // terminologyContextName: we don't know it, so we set it to empty by default + categoryCodeValue, categoryCodingSchemeDesignator, "", + typeCodeValue, typeCodingSchemeDesignator, "", + typeModifierCodeValue, typeModifierCodingSchemeDesignator, "", + "", // regionContextName: we don't know it, so we set it to empty by default + regionCodeValue, regionCodingSchemeDesignator, "", + regionModifierCodeValue, regionModifierCodingSchemeDesignator, ""); + for (std::string& compatibleColorNodeID : compatibleColorNodeIDs) + { + vtkMRMLColorNode* compatibleColorNode = vtkMRMLColorNode::SafeDownCast(scene->GetNodeByID(compatibleColorNodeID)); + if (!compatibleColorNode) + { + continue; + } + int indexInColorTable = vtkSlicerTerminologiesModuleLogic::GetColorIndexByTerminology( + compatibleColorNode, terminologyStr.c_str(), true /*ignoreContextName*/); + if (indexInColorTable > -1) + { + foundColorNodeIDs.push_back(compatibleColorNodeID); + if (foundColorIndices) + { + foundColorIndices->InsertNextValue(indexInColorTable); + } + } + } + return foundColorNodeIDs; +} diff --git a/Modules/Loadable/Terminologies/Logic/vtkSlicerTerminologiesModuleLogic.h b/Modules/Loadable/Terminologies/Logic/vtkSlicerTerminologiesModuleLogic.h index 70a9fea4982..ab1d7c0b8da 100644 --- a/Modules/Loadable/Terminologies/Logic/vtkSlicerTerminologiesModuleLogic.h +++ b/Modules/Loadable/Terminologies/Logic/vtkSlicerTerminologiesModuleLogic.h @@ -103,6 +103,12 @@ class VTK_SLICER_TERMINOLOGIES_LOGIC_EXPORT vtkSlicerTerminologiesModuleLogic : /// Python accessor variant of \sa GetLoadedRegionContextNames void GetLoadedRegionContextNames(vtkStringArray* regionContextNames); + /// Get list of color node IDs that contain terminology information. + std::vector GetCompatibleColorNodeIDs(); + + /// Return the first compatible color node that has a matching name. + vtkMRMLColorNode* GetFirstCompatibleColorNodeByName(std::string name); + /// Get terminology categories from a terminology as collection of \sa vtkSlicerTerminologyCategory container objects /// \param categories Output argument containing all the \sa vtkSlicerTerminologyCategory objects created /// from the categories found in the given terminology @@ -114,6 +120,15 @@ class VTK_SLICER_TERMINOLOGIES_LOGIC_EXPORT vtkSlicerTerminologiesModuleLogic : /// \return Success flag bool FindCategoriesInTerminology(std::string terminologyName, std::vector& categories, std::string search); + std::vector FindColorNodes( + std::string categoryCodingSchemeDesignator, std::string categoryCodeValue, + std::string typeCodingSchemeDesignator, std::string typeCodeValue, + std::string typeModifierCodingSchemeDesignator, std::string typeModifierCodeValue, + std::string regionCodingSchemeDesignator, std::string regionCodeValue, + std::string regionModifierCodingSchemeDesignator, std::string regionModifierCodeValue, + std::vector preferredColorNodeNames, + vtkIntArray* foundColorIndices=nullptr); + /// Return collection of vtkSlicerTerminologyEntry objects designated by the given codes. /// \param preferredTerminologyNames List of terminology names in order of preference. If an empty list is provided then all terminologies are searched. std::vector FindTerminologyNames( diff --git a/Modules/Loadable/Terminologies/Widgets/qMRMLSimpleColorTableView.cxx b/Modules/Loadable/Terminologies/Widgets/qMRMLSimpleColorTableView.cxx index 417f4f05883..399bad24785 100644 --- a/Modules/Loadable/Terminologies/Widgets/qMRMLSimpleColorTableView.cxx +++ b/Modules/Loadable/Terminologies/Widgets/qMRMLSimpleColorTableView.cxx @@ -19,6 +19,7 @@ ==============================================================================*/ // Qt includes +#include #include #include #include @@ -56,6 +57,8 @@ void qMRMLSimpleColorTableViewPrivate::init() { Q_Q(qMRMLSimpleColorTableView); + q->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + qMRMLColorModel* colorModel = new qMRMLColorModel(q); QSortFilterProxyModel* sortFilterModel = new QSortFilterProxyModel(q); sortFilterModel->setSourceModel(colorModel); @@ -120,7 +123,7 @@ vtkMRMLColorNode* qMRMLSimpleColorTableView::mrmlColorNode()const } //------------------------------------------------------------------------------ -void qMRMLSimpleColorTableView::selectColorByIndex(int colorIndex)const +bool qMRMLSimpleColorTableView::selectColorByIndex(int colorIndex) { QSortFilterProxyModel* sortFilterModel = this->sortFilterProxyModel(); qMRMLColorModel* colorModel = this->colorModel(); @@ -128,7 +131,7 @@ void qMRMLSimpleColorTableView::selectColorByIndex(int colorIndex)const if (colorNode == nullptr) { qCritical() << Q_FUNC_INFO << ": Invalid color node in table view"; - return; + return false; } QModelIndexList foundIndices = colorModel->match(colorModel->index(0,0), qMRMLItemDelegate::ColorEntryRole, @@ -137,9 +140,34 @@ void qMRMLSimpleColorTableView::selectColorByIndex(int colorIndex)const { qCritical() << Q_FUNC_INFO << ": Failed to find color model index by color index " << colorIndex << " in color node " << colorNode->GetName(); - return; + return false; } QModelIndex foundIndex = sortFilterModel->mapFromSource(foundIndices[0]); this->selectionModel()->select(foundIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows); + + // set current index to the found index, but in the label column + QModelIndex foundLabelIndex = foundIndex.sibling(foundIndex.row(), colorModel->labelColumn()); + this->selectionModel()->setCurrentIndex(foundLabelIndex, QItemSelectionModel::Current); + + // Scroll to the item to make it visible + this->scrollTo(foundIndex); + return true; +} + +//------------------------------------------------------------------------------ +int qMRMLSimpleColorTableView::selectedColorIndex()const +{ + QSortFilterProxyModel* sortFilterModel = this->sortFilterProxyModel(); + if (!sortFilterModel) + { + return -1; + } + qMRMLColorModel* colorModel = this->colorModel(); + if (!colorModel) + { + return -1; + } + int colorIndex = colorModel->colorFromIndex(sortFilterModel->mapToSource(this->currentIndex())); + return colorIndex; } diff --git a/Modules/Loadable/Terminologies/Widgets/qMRMLSimpleColorTableView.h b/Modules/Loadable/Terminologies/Widgets/qMRMLSimpleColorTableView.h index e454aaf15ad..796c48143fe 100644 --- a/Modules/Loadable/Terminologies/Widgets/qMRMLSimpleColorTableView.h +++ b/Modules/Loadable/Terminologies/Widgets/qMRMLSimpleColorTableView.h @@ -47,12 +47,15 @@ class Q_SLICER_MODULE_TERMINOLOGIES_WIDGETS_EXPORT qMRMLSimpleColorTableView : p Q_INVOKABLE qMRMLColorModel* colorModel()const; Q_INVOKABLE QSortFilterProxyModel* sortFilterProxyModel()const; + Q_INVOKABLE int selectedColorIndex()const; + public slots: void setMRMLColorNode(vtkMRMLColorNode* colorNode); /// Utility function to simply connect signals/slots with Qt Designer void setMRMLColorNode(vtkMRMLNode* colorNode); - /// Select row in table by color index - void selectColorByIndex(int colorIndex)const; + /// Select row in table by color index and scroll to it. + /// Returns true on success. + bool selectColorByIndex(int colorIndex); protected: QScopedPointer d_ptr; diff --git a/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyItemDelegate.cxx b/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyItemDelegate.cxx index 06601dde267..9ee8ddcf895 100644 --- a/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyItemDelegate.cxx +++ b/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyItemDelegate.cxx @@ -163,8 +163,12 @@ void qSlicerTerminologyItemDelegate::setEditorData(QWidget *editor, const QModel if (logic) { logic->DeserializeTerminologyEntry(terminologyString.toUtf8().constData(), terminologyEntry); - // Get default color and other metadata from loaded terminologies - logic->UpdateEntryFromLoadedTerminologies(terminologyEntry); + // Get default color and other metadata from loaded terminologies, but only for non-color nodes, + // because terminologies in color nodes are fully defined in the table, so we do not no need to look up additional metadata. + if (!logic->GetFirstCompatibleColorNodeByName(terminologyEntry->GetTerminologyContextName() ? terminologyEntry->GetTerminologyContextName() : "")) + { + logic->UpdateEntryFromLoadedTerminologies(terminologyEntry); + } } // Get metadata diff --git a/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyNavigatorWidget.cxx b/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyNavigatorWidget.cxx index a201ff0aff2..c0bbe3f004c 100644 --- a/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyNavigatorWidget.cxx +++ b/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyNavigatorWidget.cxx @@ -135,9 +135,14 @@ class qSlicerTerminologyNavigatorWidgetPrivate: public Ui_qSlicerTerminologyNavi void resetCurrentType(); /// Reset current type modifier name and container object void resetCurrentTypeModifier(); + /// Reset current region name and container object + void resetCurrentRegion(); + /// Reset current region modifier name and container object + void resetCurrentRegionModifier(); /// Set name from current selection and set it to name text box void setNameFromCurrentTerminology(); + /// Set recommended color from current selection to color picker void setRecommendedColorFromCurrentTerminology(); /// Get recommended color stored in terminology. @@ -148,11 +153,6 @@ class qSlicerTerminologyNavigatorWidgetPrivate: public Ui_qSlicerTerminologyNavi /// If no color is found then an invalid color object is returned QColor recommendedColorForType(std::string terminologyName, vtkSlicerTerminologyCategory* category, vtkSlicerTerminologyType* type); - /// Reset current region name and container object - void resetCurrentRegion(); - /// Reset current region modifier name and container object - void resetCurrentRegionModifier(); - /// Find item in category table widget corresponding to a given category QTableWidgetItem* findTableWidgetItemForCategory(vtkSlicerTerminologyCategory* category); /// Find item in (type or region) table widget corresponding to a given type @@ -161,31 +161,76 @@ class qSlicerTerminologyNavigatorWidgetPrivate: public Ui_qSlicerTerminologyNavi /// \return -1 if not found int findComboBoxIndexForModifier(ctkComboBox* comboBox, vtkSlicerTerminologyType* modifier); - /// Select given terminology context in the combobox - void selectTerminologyContext(QString contextName); + /// Get last terminology context selected by the user that contains the currently set terminology entry + bool findTerminology(vtkSlicerTerminologyEntry* entry, QString& terminologyName, QString& colorNodeId, int& colorIndexInColorTable); + + /// Set current terminology to widget + void setCurrentTerminology(QString terminologyName, QString colorNodeID); + /// Set current category to widget. + /// Only used when setting the category from a given entry to the widget! + /// \return Flag indicating whether the given category was found in the category table + bool setCurrentCategory(vtkSlicerTerminologyCategory* category); + /// Set current type to widget + /// \return Flag indicating whether the given type was found in the type table + bool setCurrentType(vtkSlicerTerminologyType* type); + /// Set current type modifier to widget + /// \return Flag indicating whether the given modifier was found in the combobox + bool setCurrentTypeModifier(vtkSlicerTerminologyType* modifier); + /// Set current region context to widget + void setCurrentRegionContext(QString contextName); + /// Set current region to widget + /// \return Flag indicating whether the given region was found in the region table + bool setCurrentRegion(vtkSlicerTerminologyType* region); + /// Set current region modifier to widget + /// \return Flag indicating whether the given modifier was found in the combobox + bool setCurrentRegionModifier(vtkSlicerTerminologyType* modifier); + + /// Update widget UI based on the current category selection + void updateWidgetFromCurrentCategory(); + + /// Populate terminology combobox based on current selection + void populateTerminologyComboBox(); + /// Populate category table based on selected terminology and category search term + void populateCategoryTable(); + /// Populate type table based on selected category and type search term + void populateTypeTable(); + /// Populate type modifier combobox based on current selection + void populateTypeModifierComboBox(); + + /// Populate region context combobox based on current selection + void populateRegionContextComboBox(); + /// Populate region table based on selected region context and type search term + void populateRegionTable(); + /// Populate region modifier combobox based on current selection + void populateRegionModifierComboBox(); + + /// Copy terminology or region context file to user folder + void copyContextToUserDirectory(QString filePath); public: - /// Name (SegmentationCategoryTypeContextName) of the current terminology + /// Name (SegmentationCategoryTypeContextName) of the current terminology. Color node name if the current terminology is a color node. QString CurrentTerminologyName; + /// Color node ID of the current terminology. Empty if the current terminology is not a color node. + QString CurrentColorNodeID; /// Object containing the details of the current category. /// This is the category containing the current type. It does not reflect UI selection - vtkSlicerTerminologyCategory* CurrentCategoryObject; + vtkSmartPointer CurrentCategoryObject; /// Category objects of the currently selected categories in the UI /// Used for populating the type list when categories are selected QList > SelectedCategoryObjects; /// Object containing the details of the current type - vtkSlicerTerminologyType* CurrentTypeObject; + vtkSmartPointer CurrentTypeObject; /// Object containing the details of the current type modifier if any - vtkSlicerTerminologyType* CurrentTypeModifierObject; + vtkSmartPointer CurrentTypeModifierObject; /// Name (RegionContextName) of the current region context QString CurrentRegionContextName; /// Object containing the details of the current region - vtkSlicerTerminologyType* CurrentRegionObject; + vtkSmartPointer CurrentRegionObject; /// Object containing the details of the current region modifier if any - vtkSlicerTerminologyType* CurrentRegionModifierObject; + vtkSmartPointer CurrentRegionModifierObject; /// Name of the "no selected type" item QString NoneItemName; @@ -214,43 +259,17 @@ qSlicerTerminologyNavigatorWidgetPrivate::qSlicerTerminologyNavigatorWidgetPriva , NoneItemName(QString("[ ") + qSlicerTerminologyNavigatorWidget::tr("None") + QString(" ]")) , GeneratedColor(vtkSlicerTerminologyType::INVALID_COLOR[0], vtkSlicerTerminologyType::INVALID_COLOR[1], vtkSlicerTerminologyType::INVALID_COLOR[2]) { - this->CurrentCategoryObject = vtkSlicerTerminologyCategory::New(); - this->CurrentTypeObject = vtkSlicerTerminologyType::New(); - this->CurrentTypeModifierObject = vtkSlicerTerminologyType::New(); + this->CurrentCategoryObject = vtkSmartPointer::New(); + this->CurrentTypeObject = vtkSmartPointer::New(); + this->CurrentTypeModifierObject = vtkSmartPointer::New(); - this->CurrentRegionObject = vtkSlicerTerminologyType::New(); - this->CurrentRegionModifierObject = vtkSlicerTerminologyType::New(); + this->CurrentRegionObject = vtkSmartPointer::New(); + this->CurrentRegionModifierObject = vtkSmartPointer::New(); } //----------------------------------------------------------------------------- qSlicerTerminologyNavigatorWidgetPrivate::~qSlicerTerminologyNavigatorWidgetPrivate() { - if (this->CurrentCategoryObject) - { - this->CurrentCategoryObject->Delete(); - this->CurrentCategoryObject = nullptr; - } - if (this->CurrentTypeObject) - { - this->CurrentTypeObject->Delete(); - this->CurrentTypeObject = nullptr; - } - if (this->CurrentTypeModifierObject) - { - this->CurrentTypeModifierObject->Delete(); - this->CurrentTypeModifierObject = nullptr; - } - - if (this->CurrentRegionObject) - { - this->CurrentRegionObject->Delete(); - this->CurrentRegionObject = nullptr; - } - if (this->CurrentRegionModifierObject) - { - this->CurrentRegionModifierObject->Delete(); - this->CurrentRegionModifierObject = nullptr; - } } //----------------------------------------------------------------------------- @@ -263,7 +282,7 @@ void qSlicerTerminologyNavigatorWidgetPrivate::init() this->ColorTableView = new qMRMLSimpleColorTableView(q); QSortFilterProxyModel* sortFilterModel = this->ColorTableView->sortFilterProxyModel(); qMRMLColorModel* colorModel = this->ColorTableView->colorModel(); - sortFilterModel->setFilterKeyColumn(colorModel->terminologyColumn()); + sortFilterModel->setFilterKeyColumn(colorModel->labelColumn()); sortFilterModel->setFilterRegExp("^(?!.*none).*$"); // Do not show entries with empty terminology QHBoxLayout* colorTableLayout = new QHBoxLayout(); colorTableLayout->addWidget(this->ColorTableView); @@ -350,9 +369,9 @@ void qSlicerTerminologyNavigatorWidgetPrivate::init() q, SLOT(onLoadRegionContextClicked()) ); // Populate terminology combobox with the loaded terminologies - q->populateTerminologyComboBox(); + this->populateTerminologyComboBox(); // Populate region context combobox with the loaded region contexts - q->populateRegionContextComboBox(); + this->populateRegionContextComboBox(); } //----------------------------------------------------------------------------- @@ -376,34 +395,31 @@ vtkSlicerTerminologiesModuleLogic* qSlicerTerminologyNavigatorWidgetPrivate::ter //----------------------------------------------------------------------------- void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentCategory() { - if (this->CurrentCategoryObject) - { - this->CurrentCategoryObject->Delete(); - this->CurrentCategoryObject = nullptr; - } - this->CurrentCategoryObject = vtkSlicerTerminologyCategory::New(); + this->CurrentCategoryObject = vtkSmartPointer::New(); } //----------------------------------------------------------------------------- void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentType() { - if (this->CurrentTypeObject) - { - this->CurrentTypeObject->Delete(); - this->CurrentTypeObject = nullptr; - } - this->CurrentTypeObject = vtkSlicerTerminologyType::New(); + this->CurrentTypeObject = vtkSmartPointer::New(); } //----------------------------------------------------------------------------- void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentTypeModifier() { - if (this->CurrentTypeModifierObject) - { - this->CurrentTypeModifierObject->Delete(); - this->CurrentTypeModifierObject = nullptr; - } - this->CurrentTypeModifierObject = vtkSlicerTerminologyType::New(); + this->CurrentTypeModifierObject = vtkSmartPointer::New(); +} + +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentRegion() +{ + this->CurrentRegionObject = vtkSmartPointer::New(); +} + +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentRegionModifier() +{ + this->CurrentRegionModifierObject = vtkSmartPointer::New(); } //----------------------------------------------------------------------------- @@ -545,28 +561,6 @@ QColor qSlicerTerminologyNavigatorWidgetPrivate::recommendedColorForType( return QColor(); } -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentRegion() -{ - if (this->CurrentRegionObject) - { - this->CurrentRegionObject->Delete(); - this->CurrentRegionObject = nullptr; - } - this->CurrentRegionObject = vtkSlicerTerminologyType::New(); -} - -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentRegionModifier() -{ - if (this->CurrentRegionModifierObject) - { - this->CurrentRegionModifierObject->Delete(); - this->CurrentRegionModifierObject = nullptr; - } - this->CurrentRegionModifierObject = vtkSlicerTerminologyType::New(); -} - //----------------------------------------------------------------------------- QTableWidgetItem* qSlicerTerminologyNavigatorWidgetPrivate::findTableWidgetItemForCategory(vtkSlicerTerminologyCategory* category) { @@ -641,1786 +635,1830 @@ int qSlicerTerminologyNavigatorWidgetPrivate::findComboBoxIndexForModifier(ctkCo } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidgetPrivate::selectTerminologyContext(QString contextName) +void qSlicerTerminologyNavigatorWidgetPrivate::populateTerminologyComboBox() { Q_Q(qSlicerTerminologyNavigatorWidget); + QSignalBlocker blocker(this->ComboBox_Terminology); + QSignalBlocker blocker2(this->ComboBox_Terminology_2); + this->ComboBox_Terminology->clear(); + this->ComboBox_Terminology_2->clear(); - int terminologyIndex = this->ComboBox_Terminology->findText(contextName); - if (terminologyIndex == -1) + vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic(); + if (!logic) { - qCritical() << Q_FUNC_INFO << ": Failed to find terminology with context name '" << contextName << "'"; return; } - if (terminologyIndex != this->ComboBox_Terminology->currentIndex()) - { - q->setCurrentTerminology(this->ComboBox_Terminology->itemText(terminologyIndex)); - } - QSignalBlocker blocker(this->ComboBox_Terminology); - this->ComboBox_Terminology->setCurrentIndex(terminologyIndex); - QSignalBlocker blocker2(this->ComboBox_Terminology_2); - this->ComboBox_Terminology_2->setCurrentIndex(terminologyIndex); -} - -//----------------------------------------------------------------------------- -// qSlicerTerminologyNavigatorWidget methods - -//----------------------------------------------------------------------------- -qSlicerTerminologyNavigatorWidget::qSlicerTerminologyNavigatorWidget(QWidget* _parent) - : qMRMLWidget(_parent) - , d_ptr(new qSlicerTerminologyNavigatorWidgetPrivate(*this)) -{ - Q_D(qSlicerTerminologyNavigatorWidget); - d->init(); - // Connect logic modified event (cannot call QVTK from private implementation) - vtkSlicerTerminologiesModuleLogic* logic = qSlicerTerminologyNavigatorWidgetPrivate::terminologyLogic(); - if (logic) + this->TerminologyComboboxPopulating = true; + std::vector terminologyNames; + // Note: Currently we consider all the loaded terminology contexts, and these include the ones converted + // from the compatible color table nodes in the scene. We could instead just use the compatible color + // nodes, however, we keep this conversion for now (it could be useful for saving them, or if we decide + // to use the terminology selector again for color table terminologies). + logic->GetLoadedTerminologyNames(terminologyNames); + for (std::vector::iterator termIt = terminologyNames.begin(); termIt != terminologyNames.end(); ++termIt) { - qvtkConnect(logic, vtkCommand::ModifiedEvent, this, SLOT(onLogicModified())); + this->ComboBox_Terminology->addItem(termIt->c_str()); + this->ComboBox_Terminology_2->addItem(termIt->c_str()); } -} - -//----------------------------------------------------------------------------- -qSlicerTerminologyNavigatorWidget::~qSlicerTerminologyNavigatorWidget() -{ - Q_D(qSlicerTerminologyNavigatorWidget); - if (qSlicerApplication::application()) + vtkMRMLScene* scene = logic->GetMRMLScene(); + if (scene) { - QSettings* settings = qSlicerApplication::application()->userSettings(); - settings->setValue("Terminology/ShowCategorySelector", d->CategoryExpandButton->isChecked()); - settings->setValue("Terminology/ShowRegionSelector", d->RegionExpandButton->isChecked()); + std::vector colorNodeIds = logic->GetCompatibleColorNodeIDs(); + for (std::string& colorNodeId : colorNodeIds) + { + vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast(scene->GetNodeByID(colorNodeId.c_str())); + if (!colorNode || !colorNode->GetName()) + { + continue; + } + this->ComboBox_Terminology->addItem(QString::fromUtf8(colorNode->GetName()), QString::fromStdString(colorNodeId)); + this->ComboBox_Terminology_2->addItem(QString::fromUtf8(colorNode->GetName()), QString::fromStdString(colorNodeId)); + } } + this->TerminologyComboboxPopulating = false; } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::terminologyInfo(TerminologyInfoBundle &terminologyInfo) +void qSlicerTerminologyNavigatorWidgetPrivate::populateCategoryTable() { - Q_D(qSlicerTerminologyNavigatorWidget); - this->terminologyEntry(terminologyInfo.GetTerminologyEntry()); - terminologyInfo.Name = d->lineEdit_Name->text(); - terminologyInfo.NameAutoGenerated = d->NameAutoGenerated; - terminologyInfo.Color = d->ColorPickerButton_RecommendedRGB->color(); - terminologyInfo.ColorAutoGenerated = d->ColorAutoGenerated; -} + Q_Q(qSlicerTerminologyNavigatorWidget); -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::setTerminologyInfo(TerminologyInfoBundle &terminologyInfo) -{ - Q_D(qSlicerTerminologyNavigatorWidget); + this->tableWidget_Category->clearContents(); - // Set terminology entry - if (!this->setTerminologyEntry(terminologyInfo.GetTerminologyEntry())) + if (this->CurrentTerminologyName.isEmpty()) { - qWarning() << Q_FUNC_INFO << ": Failed to set terminology entry from given terminology info bundle"; + this->tableWidget_Category->setRowCount(0); + return; } - bool noneType = (d->CurrentTypeObject->GetCodeValue() == nullptr); - // Set name - if (terminologyInfo.Name.isEmpty()) + vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic(); + if (!logic) { - // If given name is empty, then generate it from terminology (auto-generate flag will be true) - d->setNameFromCurrentTerminology(); + qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; + return; } - else - { - d->lineEdit_Name->blockSignals(true); // Only call callback function if user changes from UI - d->lineEdit_Name->setText(terminologyInfo.Name); - d->lineEdit_Name->blockSignals(false); - d->NameAutoGenerated = terminologyInfo.NameAutoGenerated; - d->pushButton_ResetName->setEnabled(!d->NameAutoGenerated && !noneType); - } + // Get category names containing the search string. If no search string then add every category + std::vector categories; + logic->FindCategoriesInTerminology( + this->CurrentTerminologyName.toUtf8().constData(), categories, this->SearchBox_Category->text().toUtf8().constData()); - // Store generated color. It is used when the selected terminology contains no recommended color - if (terminologyInfo.GeneratedColor.isValid()) + QTableWidgetItem* selectedItem = nullptr; + this->tableWidget_Category->setRowCount(categories.size()); + int index = 0; + std::vector::iterator idIt; + for (idIt = categories.begin(); idIt != categories.end(); ++idIt, ++index) { - d->GeneratedColor = terminologyInfo.GeneratedColor; + vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedCategoryId = (*idIt); + QString addedCategoryName(addedCategoryId.CodeMeaning.c_str()); + QTableWidgetItem* addedCategoryItem = new QTableWidgetItem(addedCategoryName); + addedCategoryItem->setData(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole, QString(addedCategoryId.CodingSchemeDesignator.c_str())); + addedCategoryItem->setData(qSlicerTerminologyNavigatorWidget::CodeValueRole, QString(addedCategoryId.CodeValue.c_str())); + this->tableWidget_Category->setItem(index, 0, addedCategoryItem); + + if (this->CurrentCategoryObject->GetCodingSchemeDesignator() + && !addedCategoryId.CodingSchemeDesignator.compare(this->CurrentCategoryObject->GetCodingSchemeDesignator()) + && this->CurrentCategoryObject->GetCodeValue() + && !addedCategoryId.CodeValue.compare(this->CurrentCategoryObject->GetCodeValue())) + { + selectedItem = addedCategoryItem; + } } - // Set color - if (!terminologyInfo.Color.isValid()) + // Select category if selection was valid and item shows up in search + if (selectedItem) { - // If given color is invalid, then get it from terminology (auto-generate flag will be true) - d->setRecommendedColorFromCurrentTerminology(); + this->tableWidget_Category->setCurrentItem(selectedItem); } + // Otherwise select all else { - d->ColorPickerButton_RecommendedRGB->blockSignals(true); // Only call callback function if user changes from UI - d->ColorPickerButton_RecommendedRGB->setColor(terminologyInfo.Color); - d->ColorPickerButton_RecommendedRGB->blockSignals(false); - - d->ColorAutoGenerated = terminologyInfo.ColorAutoGenerated; - - bool enableResetColor = !d->ColorAutoGenerated - || (d->ColorPickerButton_RecommendedRGB->color() != d->terminologyRecommendedColor()); - d->pushButton_ResetColor->setEnabled(enableResetColor && !noneType); - } - - // Set last selected terminology context if empty entry was selected - QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings(); - if (settings->contains("Terminology/LastTerminologyContexts")) - { - QStringList lastTerminologyContextNames = settings->value("Terminology/LastTerminologyContexts").toStringList(); - if (lastTerminologyContextNames.size() > 0) - { - d->selectTerminologyContext(lastTerminologyContextNames[0]); - } + this->tableWidget_Category->selectAll(); } } //----------------------------------------------------------------------------- -bool qSlicerTerminologyNavigatorWidget::terminologyEntry(vtkSlicerTerminologyEntry* entry) +void qSlicerTerminologyNavigatorWidgetPrivate::populateTypeTable() { - Q_D(qSlicerTerminologyNavigatorWidget); - - if (!entry) - { - qCritical() << Q_FUNC_INFO << ": Invalid terminology entry object"; - return false; - } - if (!entry->GetCategoryObject() || !entry->GetTypeObject() || !entry->GetTypeModifierObject() - || !entry->GetRegionObject() || !entry->GetRegionModifierObject() ) - { - qCritical() << Q_FUNC_INFO << ": Invalid terminology entry given"; - // Invalidate whole terminology entry - entry->SetTerminologyContextName(nullptr); - entry->SetRegionContextName(nullptr); - return false; - } - - // Terminology name - if (d->CurrentTerminologyName.isEmpty()) - { - qCritical() << Q_FUNC_INFO << ": No terminology selected"; - return false; - } - entry->SetTerminologyContextName(d->CurrentTerminologyName.toUtf8().constData()); - - // Terminology category - if (!d->CurrentCategoryObject) - { - qCritical() << Q_FUNC_INFO << ": No terminology category selected"; - return false; - } - entry->GetCategoryObject()->Copy(d->CurrentCategoryObject); + Q_Q(qSlicerTerminologyNavigatorWidget); - // Terminology type - if (!d->CurrentTypeObject) - { - qCritical() << Q_FUNC_INFO << ": No terminology type selected"; - return false; - } - entry->GetTypeObject()->Copy(d->CurrentTypeObject); + this->tableWidget_Type->clearContents(); - // Terminology type modifier - if (d->CurrentTypeModifierObject) + // Collect selected categories. Use current category if selected list is empty and current is valid + // (selection happens from UI, current is set when setting from outside using setTerminologyEntry) + QList selectedCategories; + foreach(vtkSmartPointer category, this->SelectedCategoryObjects) { - entry->GetTypeModifierObject()->Copy(d->CurrentTypeModifierObject); + selectedCategories << category.GetPointer(); } - else + if (!selectedCategories.count() && this->CurrentCategoryObject && this->CurrentCategoryObject->GetCodeValue()) { - entry->GetTypeModifierObject()->Initialize(); + selectedCategories << this->CurrentCategoryObject; } - // Region context name - if (!d->CurrentRegionContextName.isEmpty()) + // Empty table only contains none item + if (this->CurrentTerminologyName.isEmpty() || selectedCategories.count() == 0) { - entry->SetRegionContextName(d->CurrentRegionContextName.toUtf8().constData()); + this->tableWidget_Type->setRowCount(1); + QTableWidgetItem* noneItem = new QTableWidgetItem(this->NoneItemName); + this->tableWidget_Type->setItem(0, 0, noneItem); + this->tableWidget_Type->setCurrentItem(noneItem); + return; } - // Region - if (d->CurrentCategoryObject->GetShowAnatomy() && d->CurrentRegionObject) - { - entry->GetRegionObject()->Copy(d->CurrentRegionObject); - } - else + vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic(); + if (!logic) { - entry->GetRegionObject()->Initialize(); + qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; + return; } - // Region modifier - if (d->CurrentCategoryObject->GetShowAnatomy() && d->CurrentRegionModifierObject) + // Get types in selected categories containing the search string. If no search string then add every type + struct TypeInfo { - entry->GetRegionModifierObject()->Copy(d->CurrentRegionModifierObject); - } - else + vtkSlicerTerminologiesModuleLogic::CodeIdentifier id; + vtkSlicerTerminologyCategory* category; + QColor color; + }; + std::vector types; + std::string searchTerm(this->SearchBox_Type->text().toUtf8().constData()); + std::vector>::iterator typeObjIt; + std::set existingTypesSchemeValue; + foreach(vtkSlicerTerminologyCategory * category, selectedCategories) { - entry->GetRegionModifierObject()->Initialize(); - } + std::vector typesInCategory; + std::vector> typesObjectsInCategory; + vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId = vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(category); - return true; + logic->FindTypesInTerminologyCategory( + this->CurrentTerminologyName.toUtf8().constData(), + vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(category), + typesInCategory, searchTerm, &typesObjectsInCategory); + + std::vector::iterator idIt; + for (idIt = typesInCategory.begin(), typeObjIt = typesObjectsInCategory.begin(); idIt != typesInCategory.end(); ++idIt, ++typeObjIt) + { + // Determine if type already exists in list + std::string typesSchemeValue = idIt->CodeValue + idIt->CodingSchemeDesignator; + if (existingTypesSchemeValue.find(typesSchemeValue) != existingTypesSchemeValue.end()) + { + // duplicate + continue; + } + + // Add type + TypeInfo typeInfo; + typeInfo.id = *idIt; + typeInfo.category = category; + typeInfo.color = this->recommendedColorForType(this->CurrentTerminologyName.toUtf8().constData(), category, *typeObjIt); + types.push_back(typeInfo); + + // Store type-category relationship + existingTypesSchemeValue.insert(typesSchemeValue); + } + + } + + QTableWidgetItem* selectedItem = nullptr; + + // Show none item only if search term is empty (if user is searching then they want an actual type) + int noneTypeExists = 0; + QTableWidgetItem* noneItem = nullptr; + if (searchTerm.empty()) + { + noneTypeExists = 1; + noneItem = new QTableWidgetItem(this->NoneItemName); + this->tableWidget_Type->setRowCount(types.size() + noneTypeExists); + this->tableWidget_Type->setItem(0, 0, noneItem); + } + else + { + this->tableWidget_Type->setRowCount(types.size()); + } + + // Add type items to table + int typeIndex = 0; + vtkNew typeObject; + std::vector::iterator typeIt; + for (typeIt = types.begin(); typeIt != types.end(); ++typeIt, ++typeIndex) + { + const vtkSlicerTerminologiesModuleLogic::CodeIdentifier& addedTypeId = typeIt->id; + QString addedTypeName(addedTypeId.CodeMeaning.c_str()); + QTableWidgetItem* addedTypeItem = new QTableWidgetItem(addedTypeName); + addedTypeItem->setData(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole, QString(addedTypeId.CodingSchemeDesignator.c_str())); + addedTypeItem->setData(qSlicerTerminologyNavigatorWidget::CodeValueRole, QString(addedTypeId.CodeValue.c_str())); + // Reference containing category so that it can be set when type is selected + vtkSlicerTerminologyCategory* category = typeIt->category; + addedTypeItem->setData(qSlicerTerminologyNavigatorWidget::CategoryCodingSchemeDesignatorRole, QString(category->GetCodingSchemeDesignator())); + addedTypeItem->setData(qSlicerTerminologyNavigatorWidget::CategoryCodeValueRole, QString(category->GetCodeValue())); + addedTypeItem->setData(qSlicerTerminologyNavigatorWidget::CategoryCodeMeaningRole, QString(category->GetCodeMeaning())); + QString tooltip = QString("Category: %1 (anatomy:%2)").arg(category->GetCodeMeaning()).arg( + (category->GetShowAnatomy() ? "available" : "N/A")); + addedTypeItem->setToolTip(tooltip); + + if (typeIt->color.isValid()) + { + addedTypeItem->setData(Qt::DecorationRole, typeIt->color); + } + + // Insert type item + this->tableWidget_Type->setItem(typeIndex + noneTypeExists, 0, addedTypeItem); + + if (this->CurrentTypeObject->GetCodingSchemeDesignator() + && !addedTypeId.CodingSchemeDesignator.compare(this->CurrentTypeObject->GetCodingSchemeDesignator()) + && this->CurrentTypeObject->GetCodeValue() + && !addedTypeId.CodeValue.compare(this->CurrentTypeObject->GetCodeValue())) + { + selectedItem = addedTypeItem; + } + } + + if (selectedItem) + { + // Select type if selection was valid and item shows up in search + this->tableWidget_Type->setCurrentItem(selectedItem); + } + else if (noneItem) + { + // Select none item if it exists (no search and no selected item specified) + this->tableWidget_Type->setCurrentItem(noneItem); + } + else + { + // Select first type otherwise + this->tableWidget_Type->setCurrentCell(0, 0); + } } //----------------------------------------------------------------------------- -bool qSlicerTerminologyNavigatorWidget::setTerminologyEntry(vtkSlicerTerminologyEntry* entry) +void qSlicerTerminologyNavigatorWidgetPrivate::populateTypeModifierComboBox() { - Q_D(qSlicerTerminologyNavigatorWidget); + Q_Q(qSlicerTerminologyNavigatorWidget); - if (!entry) + this->ComboBox_TypeModifier->clear(); + + if (this->CurrentTerminologyName.isEmpty() || !this->CurrentCategoryObject || !this->CurrentTypeObject || !this->CurrentTypeObject->GetCodeValue()) { - qCritical() << Q_FUNC_INFO << ": Invalid terminology entry object"; - return false; + this->ComboBox_TypeModifier->setEnabled(false); + return; } - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); - if (!logic) + // If current type has no modifiers then leave it empty and disable + if (!this->CurrentTypeObject->GetHasModifiers()) { - return false; + this->ComboBox_TypeModifier->setEnabled(false); + return; } - // If entry is empty then select none type for no terminology - if (!entry->GetTerminologyContextName() && !entry->GetCategoryObject()->GetCodeValue()) + vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic(); + if (!logic) { - this->setCurrentType(nullptr); - return true; + qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; + return; } - // Get category and type objects - vtkSlicerTerminologyCategory* categoryObject = entry->GetCategoryObject(); - if (!categoryObject) - { - return false; // The terminology is not invalid but empty - } + int selectedIndex = 0; - // Get list of last terminology contexts selected by the user from application settings - std::vector preferredTerminologyNames; - QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings(); - if (settings->contains("Terminology/LastTerminologyContexts")) + // Add none item so that no modifier can be selected even if there are options + this->ComboBox_TypeModifier->addItem(qSlicerTerminologyNavigatorWidget::tr("No type modifier")); + + // Get type modifier names + std::vector typeModifiers; + logic->GetTypeModifiersInTerminologyType( + this->CurrentTerminologyName.toUtf8().constData(), + vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(this->CurrentCategoryObject), + vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(this->CurrentTypeObject), + typeModifiers); + + int index = 1; // None modifier has index 0 + std::vector::iterator idIt; + for (idIt = typeModifiers.begin(); idIt != typeModifiers.end(); ++idIt, ++index) { - QStringList lastTerminologyContextNames = settings->value("Terminology/LastTerminologyContexts").toStringList(); - for (auto name : lastTerminologyContextNames) + vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedTypeModifierId = (*idIt); + QString addedTypeModifierName(addedTypeModifierId.CodeMeaning.c_str()); + + QMap userData; + userData[QString::number(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole)] = QString(addedTypeModifierId.CodingSchemeDesignator.c_str()); + userData[QString::number(qSlicerTerminologyNavigatorWidget::CodeValueRole)] = QString(addedTypeModifierId.CodeValue.c_str()); + this->ComboBox_TypeModifier->addItem(addedTypeModifierName, QVariant(userData)); + + if (selectedIndex == -1 && !addedTypeModifierName.compare(this->CurrentTypeModifierObject->GetCodeMeaning())) { - preferredTerminologyNames.push_back(name.toUtf8().constData()); + selectedIndex = index; } } - // Get last terminology context selected by the user that contains the currently set terminology entry - std::string categoryScheme = categoryObject->GetCodingSchemeDesignator(); - std::string categoryValue = categoryObject->GetCodeValue(); - std::string typeScheme; - std::string typeValue; - vtkSlicerTerminologyType* typeObject = entry->GetTypeObject(); - if (typeObject) + // Select modifier if selection was valid + if (selectedIndex != -1) { - typeScheme = typeObject->GetCodingSchemeDesignator(); - typeValue = typeObject->GetCodeValue(); + this->ComboBox_TypeModifier->setCurrentIndex(selectedIndex); } - std::string typeModifierScheme; - std::string typeModifierValue; - vtkSlicerTerminologyType* typeModifierObject = entry->GetTypeModifierObject(); - if (typeModifierObject) +} + +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidgetPrivate::setCurrentTerminology(QString terminologyName, QString colorNodeID) +{ + Q_Q(qSlicerTerminologyNavigatorWidget); + vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic(); + if (!logic) { - typeModifierScheme = typeModifierObject->GetCodingSchemeDesignator(); - typeModifierValue = typeModifierObject->GetCodeValue(); + qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; + return; } - vtkSlicerTerminologyType* regionObject = entry->GetRegionObject(); - std::string regionScheme; - std::string regionValue; - if (regionObject) + + // If no change then nothing to do + if (this->CurrentTerminologyName == terminologyName && this->CurrentColorNodeID == colorNodeID) { - regionScheme = regionObject->GetCodingSchemeDesignator(); - regionValue = regionObject->GetCodeValue(); + return; } - vtkSlicerTerminologyType* regionModifierObject = entry->GetRegionModifierObject(); - std::string regionModifierScheme; - std::string regionModifierValue; - if (regionModifierObject) + + int terminologyIndex = -1; + if (colorNodeID.isEmpty()) { - regionModifierScheme = regionModifierObject->GetCodingSchemeDesignator(); - regionModifierValue = regionModifierObject->GetCodeValue(); + terminologyIndex = this->ComboBox_Terminology->findText(terminologyName); } - - std::vector foundColorTableNodeIds; - std::vector foundTerminologyNames; - /* - TODO: - foundColorTableNodeIds = logic->FindColorTableNodes( - categoryScheme, categoryValue, typeScheme, typeValue, typeModifierScheme, typeModifierValue, - regionScheme, regionValue, regionModifierScheme, regionModifierValue, - preferredTerminologyNames); - */ - if (foundColorTableNodeIds.empty()) + else { - std::vector foundTerminologyNames = logic->FindTerminologyNames( - categoryScheme, categoryValue, typeScheme, typeValue, typeModifierScheme, typeModifierValue, - preferredTerminologyNames); + terminologyIndex = this->ComboBox_Terminology->findData(colorNodeID); } - if (!preferredTerminologyNames.empty() && (foundColorTableNodeIds.empty() && foundTerminologyNames.empty())) { - // Preferred terminologies do not contain the item, try to get first terminology containing it among all loaded contexts - /* - TODO: - foundColorTableNodeIds = logic->FindColorTableNodes( - categoryScheme, categoryValue, typeScheme, typeValue, typeModifierScheme, typeModifierValue, - regionScheme, regionValue, regionModifierScheme, regionModifierValue, - std::vector()); - */ - - foundTerminologyNames = logic->FindTerminologyNames( - categoryScheme, categoryValue, typeScheme, typeValue, typeModifierScheme, typeModifierValue, - std::vector()); + QSignalBlocker blocker(this->ComboBox_Terminology); + QSignalBlocker blocker2(this->ComboBox_Terminology_2); + this->ComboBox_Terminology->setCurrentIndex(terminologyIndex); + this->ComboBox_Terminology_2->setCurrentIndex(terminologyIndex); } - bool returnValue = true; - if (!foundColorTableNodeIds.empty()) + // Save last used terminology context in application settings + if (!this->TerminologyComboboxPopulating) { - std::string colorTableNodeID = foundColorTableNodeIds.front(); - // Set selection in color table - vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast(logic->GetMRMLScene()->GetNodeByID(colorTableNodeID)); - if (!colorNode) + QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings(); + QStringList lastTerminologyContextNames = settings->value("Terminology/LastTerminologyContexts").toStringList(); + if (!lastTerminologyContextNames.isEmpty()) { - qCritical() << Q_FUNC_INFO << ": Failed to find color node by ID " << colorTableNodeID.c_str(); - return false; + if (lastTerminologyContextNames.size() == 1 && lastTerminologyContextNames[0] == terminologyName) + { + // Nothing to change + return; + } + // Remove the terminology name from the list so that there are no duplicate entries in the list + lastTerminologyContextNames.removeOne(terminologyName); + // Prepend terminology name to the list so that the last used terminology is first + lastTerminologyContextNames.insert(0, terminologyName); } - vtkNew entry; - this->terminologyEntry(entry); - int foundColor = vtkSlicerTerminologiesModuleLogic::GetColorIndexByTerminology(colorNode, logic->SerializeTerminologyEntry(entry).c_str()); - if (foundColor >= 0) + else { - d->ColorTableView->selectColorByIndex(foundColor); + lastTerminologyContextNames.push_back(terminologyName); } + settings->setValue("Terminology/LastTerminologyContexts", lastTerminologyContextNames); } - else + + // Reset current category, type, and type modifier + this->resetCurrentCategory(); + this->resetCurrentType(); + this->resetCurrentTypeModifier(); + + // Set current terminology + this->CurrentTerminologyName = terminologyName; + this->CurrentColorNodeID = colorNodeID; + if (terminologyName.isEmpty()) { - QString terminologyContextNameToSelect(foundTerminologyNames.empty() ? "" : foundTerminologyNames[0].c_str()); + return; + } - // Select terminology - d->selectTerminologyContext(terminologyContextNameToSelect); + bool terminologyIsColorTable = !colorNodeID.isEmpty(); - // Select category - bool returnValue = true; - if (!this->setCurrentCategory(categoryObject)) - { - qCritical() << Q_FUNC_INFO << ": Failed to find category with name " << (categoryObject->GetCodeMeaning() ? categoryObject->GetCodeMeaning() : "NULL"); - returnValue = false; - } + if (terminologyIsColorTable) + { + // Selected terminology is a color table + vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast( + logic->GetMRMLScene()->GetNodeByID(colorNodeID.toUtf8())); + this->ColorTableView->setMRMLColorNode(colorNode); + } + else + { + // Selected terminology is loaded terminology JSON + // Populate category table, and reset type table and type modifier combobox + this->populateCategoryTable(); + this->populateTypeTable(); + this->populateTypeModifierComboBox(); - // Select type - if (!typeObject) + // Only enable category table if there are items in it + if (this->tableWidget_Category->rowCount() == 0) { - qCritical() << Q_FUNC_INFO << ": No type object in terminology entry"; - returnValue = false; + this->tableWidget_Category->setEnabled(!this->SearchBox_Type->text().isEmpty()); // Might be empty because of a search + //this->tableWidget_Type->setEnabled(false); + this->SearchBox_Type->setEnabled(false); + this->ComboBox_TypeModifier->setEnabled(false); } - else if (!this->setCurrentType(typeObject)) + else { - qCritical() << Q_FUNC_INFO << ": Failed to find type with name " << (typeObject->GetCodeMeaning() ? typeObject->GetCodeMeaning() : "NULL"); - returnValue = false; + this->tableWidget_Category->setEnabled(true); + this->SearchBox_Category->setEnabled(true); } + } - // Select type modifier - if (typeObject && typeObject->GetHasModifiers() && typeModifierObject && typeModifierObject->GetCodeValue()) - { - if (!this->setCurrentTypeModifier(typeModifierObject)) - { - qCritical() << Q_FUNC_INFO << ": Failed to find type modifier with name" - << (typeModifierObject->GetCodeMeaning() ? typeModifierObject->GetCodeMeaning() : "NULL"); - returnValue = false; - } - } + // Show widgets corresponding to selection + this->frame_TerminologyEntry->setVisible(!terminologyIsColorTable); + this->frame_ColorTable->setVisible(terminologyIsColorTable); - // Set regopm context selection if category allows - if (categoryObject->GetShowAnatomy()) - { - // Select region context - QString regionContextName(entry->GetRegionContextName() ? entry->GetRegionContextName() : ""); - if (regionContextName.isEmpty()) - { - QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings(); - if (settings->contains("Terminology/LastRegionContext")) - { - QString lastRegionContextName = settings->value("Terminology/LastRegionContext").toString(); - if (!lastRegionContextName.isEmpty()) - { - regionContextName = lastRegionContextName; - } - } - } - if (!regionContextName.isEmpty()) // Optional - { - int regionContextIndex = d->ComboBox_RegionContext->findText(regionContextName); - if (regionContextIndex == -1) - { - qCritical() << Q_FUNC_INFO << ": Failed to find region context with context name " << regionContextName; - returnValue = false; - } - if (regionContextIndex != d->ComboBox_RegionContext->currentIndex()) - { - this->setCurrentRegionContext(d->ComboBox_RegionContext->itemText(regionContextIndex)); - } - d->ComboBox_RegionContext->blockSignals(true); - d->ComboBox_RegionContext->setCurrentIndex(regionContextIndex); - d->ComboBox_RegionContext->blockSignals(false); - } + // Selection is valid if there is a valid type object + emit q->selectionValidityChanged(this->CurrentTypeObject && this->CurrentTypeObject->GetCodeValue()); +} - // Select region - vtkSlicerTerminologyType* regionObject = entry->GetRegionObject(); - if (regionObject) // Optional - { - this->setCurrentRegion(regionObject); +//----------------------------------------------------------------------------- +bool qSlicerTerminologyNavigatorWidgetPrivate::setCurrentCategory(vtkSlicerTerminologyCategory* category) +{ + Q_Q(qSlicerTerminologyNavigatorWidget); - // Select region modifier - vtkSlicerTerminologyType* regionModifierObject = entry->GetRegionModifierObject(); - if (regionObject->GetHasModifiers() && regionModifierObject) - { - this->setCurrentRegionModifier(regionModifierObject); - } - } // If region is selected - } // If showAnatomy is true - else - { - // Set region context combobox selection to the last selected context anyway, so that when - // the user changes category, the selected region context is the last selected one - QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings(); - if (settings->contains("Terminology/LastRegionContext")) - { - QString lastRegionContextName = settings->value("Terminology/LastRegionContext").toString(); - int lastRegionContextIndex = d->ComboBox_RegionContext->findText(lastRegionContextName); - if (lastRegionContextIndex >= 0) - { - d->ComboBox_RegionContext->blockSignals(true); - d->ComboBox_RegionContext->setCurrentIndex(lastRegionContextIndex); - d->ComboBox_RegionContext->blockSignals(false); + // Reset current type and type modifier + this->resetCurrentType(); + this->resetCurrentTypeModifier(); + // Reset region information as well + this->resetCurrentRegion(); + this->resetCurrentRegionModifier(); - this->setCurrentRegionContext(lastRegionContextName); - } - } - } + if (!category) + { + this->resetCurrentCategory(); + qCritical() << Q_FUNC_INFO << ": Invalid category object set"; + return false; } - return returnValue; + // Set current category + this->CurrentCategoryObject->Copy(category); + + // Populate type table, and reset type modifier combobox and region widgets + this->populateTypeTable(); + this->populateTypeModifierComboBox(); + this->tableWidget_Region->setCurrentItem(nullptr); + this->populateRegionModifierComboBox(); + + // Update widget UI from current category + this->updateWidgetFromCurrentCategory(); + + // Selection is invalid until type is selected + emit q->selectionValidityChanged(false); + + // Select category if found + QTableWidgetItem* categoryItem = this->findTableWidgetItemForCategory(category); + return (categoryItem != nullptr); // Return true if category found and selected } //----------------------------------------------------------------------------- -QString qSlicerTerminologyNavigatorWidget::nameFromTerminology(vtkSlicerTerminologyEntry* entry) +void qSlicerTerminologyNavigatorWidgetPrivate::updateWidgetFromCurrentCategory() { - QString name; - if ( !entry->GetTypeObject() || !entry->GetTypeObject()->GetCodeValue() || - (entry->GetTypeObject()->GetHasModifiers() && !entry->GetTypeModifierObject()) ) - { - // Incomplete terminology selection, name is empty - return name; - } + Q_Q(qSlicerTerminologyNavigatorWidget); - // Try to set name based on '3dSlicerLabel' field in terminology entry - if ( entry->GetTypeObject()->GetSlicerLabel() - && (!entry->GetTypeObject()->GetHasModifiers() || entry->GetTypeModifierObject()->GetCodeValue() == nullptr) ) + // Only enable type table if there are items in it + if (this->tableWidget_Type->rowCount() == 1) // None item always present { - name = entry->GetTypeObject()->GetSlicerLabel(); + this->ComboBox_TypeModifier->setEnabled(false); } - else if (entry->GetTypeModifierObject()->GetSlicerLabel()) + else { - name = entry->GetTypeModifierObject()->GetSlicerLabel(); + //this->tableWidget_Type->setEnabled(true); + this->SearchBox_Type->setEnabled(true); } - // Assemble from selection if there is no label - if (name.isEmpty()) + + // Enable region controls if related flag is on + this->ComboBox_RegionContext->setEnabled(this->CurrentCategoryObject->GetShowAnatomy()); + this->tableWidget_Region->setEnabled(this->CurrentCategoryObject->GetShowAnatomy()); + this->SearchBox_Region->setEnabled(this->CurrentCategoryObject->GetShowAnatomy()); + this->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection + if (q->regionSectionVisible()) { - // Set type and modifier in name - if (entry->GetTypeObject()->GetHasModifiers() && entry->GetTypeModifierObject() && entry->GetTypeModifierObject()->GetCodeValue()) - { - //: For formatting of terminology entry with a modifier. %1 is structure name (e.g., "Kidney"), %2 is modifier (e.g., "Left") - name = tr("%1, %2") - .arg(entry->GetTypeObject()->GetCodeMeaning()) - .arg(entry->GetTypeModifierObject()->GetCodeMeaning()); - } - else - { - name = entry->GetTypeObject()->GetCodeMeaning(); - } + // Always enable expand button if panel is visible + this->RegionExpandButton->setEnabled(true); } - - if (entry->GetRegionObject() && entry->GetRegionObject()->GetCodeValue()) + else { - if (entry->GetRegionModifierObject() && entry->GetRegionModifierObject()->GetCodeValue()) - { - //: For formatting of terminology entry name. %1 is type name (e.g., "Mass"), %2 is region name (e.g., "Kidney"), %2 is region modifier (e.g., "Left") - name = tr("%1 in %2, %3") - .arg(name) - .arg(entry->GetRegionObject()->GetCodeMeaning()) - .arg(entry->GetRegionModifierObject()->GetCodeMeaning()); - } - else + // Only enable expand button if region is enabled in selected category + this->RegionExpandButton->setEnabled(this->CurrentCategoryObject->GetShowAnatomy()); + // Blink button if region is enabled to let the user know there are additional options available + if (this->CurrentCategoryObject->GetShowAnatomy()) { - //: For formatting of terminology entry name. %1 is type name (e.g., "Mass"), %2 is region name (e.g., "Liver") - name = tr("%1 in %2") - .arg(name) - .arg(entry->GetRegionObject()->GetCodeMeaning()); + this->RegionExpandButton->setDown(true); + QTimer::singleShot(50, q, SLOT(onRegionExpandButtonUp())); + QTimer::singleShot(100, q, SLOT(onRegionExpandButtonDown())); + QTimer::singleShot(150, q, SLOT(onRegionExpandButtonUp())); + QTimer::singleShot(200, q, SLOT(onRegionExpandButtonDown())); + QTimer::singleShot(250, q, SLOT(onRegionExpandButtonUp())); } } - - if (name.isEmpty()) - { - qCritical() << Q_FUNC_INFO << ": Failed to generate name"; - } - return name; } //----------------------------------------------------------------------------- -QString qSlicerTerminologyNavigatorWidget::nameFromCurrentTerminology() +bool qSlicerTerminologyNavigatorWidgetPrivate::setCurrentType(vtkSlicerTerminologyType* type) { - Q_D(qSlicerTerminologyNavigatorWidget); - if ( !d->CurrentTypeObject || - (d->CurrentTypeObject->GetHasModifiers() && !d->CurrentTypeModifierObject) ) + Q_Q(qSlicerTerminologyNavigatorWidget); + + // Reset current type modifier + this->resetCurrentTypeModifier(); + + // Null type means None selection + if (!type) { - // Incomplete terminology selection, name is empty - return QString(); + this->resetCurrentType(); + this->tableWidget_Type->blockSignals(true); + this->tableWidget_Type->setCurrentCell(0, 0); + this->tableWidget_Type->blockSignals(false); + emit q->selectionValidityChanged(true); // None selection is valid too + return false; } - vtkSmartPointer terminologyEntry = vtkSmartPointer::New(); - if (!this->terminologyEntry(terminologyEntry)) + + // Set current type + this->CurrentTypeObject->Copy(type); + + // Populate type modifier combobox + this->populateTypeModifierComboBox(); + + // Only enable type modifier combobox if there are items in it + this->ComboBox_TypeModifier->setEnabled(this->ComboBox_TypeModifier->count()); + + // With valid type selected, terminology selection becomes also valid + emit q->selectionValidityChanged(true); + + // Select type if found + QTableWidgetItem* typeItem = this->findTableWidgetItemForType(this->tableWidget_Type, type); + if (typeItem) { - // Failed to get current terminology - return QString(); + this->tableWidget_Type->blockSignals(true); + this->tableWidget_Type->setCurrentItem(typeItem); + this->tableWidget_Type->blockSignals(false); } - - return qSlicerTerminologyNavigatorWidget::nameFromTerminology(terminologyEntry); + return typeItem; // Return true if type found and selected } //----------------------------------------------------------------------------- -QColor qSlicerTerminologyNavigatorWidget::recommendedColorFromTerminology(vtkSlicerTerminologyEntry* entry) +bool qSlicerTerminologyNavigatorWidgetPrivate::setCurrentTypeModifier(vtkSlicerTerminologyType* modifier) { - QColor color; - if (!entry || !entry->GetTypeObject()) + Q_Q(qSlicerTerminologyNavigatorWidget); + + if (!modifier) { - return color; + this->resetCurrentTypeModifier(); + qCritical() << Q_FUNC_INFO << ": Invalid type modifier object set"; + return false; } - vtkSlicerTerminologyType* typeObject = entry->GetTypeObject(); - if (typeObject->GetHasModifiers()) + // Set current type modifier + this->CurrentTypeModifierObject->Copy(modifier); + + // Select modifier if found + int modifierIndex = this->findComboBoxIndexForModifier(this->ComboBox_TypeModifier, modifier); + if (modifierIndex != -1) { - // Get color from modifier if any - typeObject = entry->GetTypeModifierObject(); + this->ComboBox_TypeModifier->blockSignals(true); + this->ComboBox_TypeModifier->setCurrentIndex(modifierIndex); + this->ComboBox_TypeModifier->blockSignals(false); } - - unsigned char colorChar[3] = {0,0,0}; - typeObject->GetRecommendedDisplayRGBValue(colorChar); - color.setRgb(colorChar[0], colorChar[1], colorChar[2]); - return color; + return (modifierIndex != -1); } //----------------------------------------------------------------------------- -QColor qSlicerTerminologyNavigatorWidget::recommendedColorFromCurrentTerminology() +void qSlicerTerminologyNavigatorWidgetPrivate::copyContextToUserDirectory(QString filePath) { - Q_D(qSlicerTerminologyNavigatorWidget); + Q_Q(qSlicerTerminologyNavigatorWidget); + vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic(); + if (!logic) + { + qCritical() << Q_FUNC_INFO << ": Invalid terminology logic"; + return; + } - QColor color; - if (d->CurrentRegionContextName.isEmpty() || !d->CurrentCategoryObject || !d->CurrentTypeObject) + // Make sure the file can be copied to the settings folder + QDir settingsFolder(logic->GetUserContextsPath()); + if (!settingsFolder.exists()) { - qWarning() << Q_FUNC_INFO << ": Invalid current terminology"; - return color; + settingsFolder.mkpath(logic->GetUserContextsPath()); } + if (!settingsFolder.exists()) + { + qCritical() << Q_FUNC_INFO << ": Settings folder '" << settingsFolder.absolutePath() << "' does not exist. Copying context file failed."; + return; + } + QString fileNameOnly = QFileInfo(filePath).fileName(); + QString targetFilePath = settingsFolder.absoluteFilePath(fileNameOnly); - vtkSlicerTerminologyType* typeObject = d->CurrentTypeObject; - if (d->CurrentTypeObject->GetHasModifiers()) + // Check if there is a file with the same name in the settings folder and ask the user in that case + if (QFile::exists(targetFilePath)) { - // Get color from modifier if any - typeObject = d->CurrentTypeModifierObject; + QString message = QString(qSlicerTerminologyNavigatorWidget::tr("There is a file with name '%1' in the stored contexts.\n\n" + "Do you wish to update the stored context file with the just loaded one?")).arg(fileNameOnly); + QMessageBox::StandardButton answer = + QMessageBox::question(nullptr, qSlicerTerminologyNavigatorWidget::tr("Context file exists"), message, + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (answer == QMessageBox::No) + { + return; + } + else + { + // Remove file before copying (copy function does not overwrite) + settingsFolder.remove(fileNameOnly); + } } - unsigned char colorChar[3] = {0,0,0}; - typeObject->GetRecommendedDisplayRGBValue(colorChar); - color.setRgb(colorChar[0], colorChar[1], colorChar[2]); - return color; + // Copy file to settings folder for automatic loading on startup + QFile file(filePath); + file.copy(targetFilePath); } //----------------------------------------------------------------------------- -bool qSlicerTerminologyNavigatorWidget::regionSectionVisible() const +void qSlicerTerminologyNavigatorWidgetPrivate::populateRegionContextComboBox() { - Q_D(const qSlicerTerminologyNavigatorWidget); + Q_Q(qSlicerTerminologyNavigatorWidget); - return d->RegionExpandButton->isChecked(); -} + this->ComboBox_RegionContext->clear(); -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::setRegionSectionVisible(bool visible) -{ - Q_D(qSlicerTerminologyNavigatorWidget); + vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic(); + if (!logic) + { + return; + } - d->RegionExpandButton->setChecked(visible); + this->RegionContextComboboxPopulating = true; + std::vector regionContextNames; + logic->GetLoadedRegionContextNames(regionContextNames); + for (std::vector::iterator anIt = regionContextNames.begin(); anIt != regionContextNames.end(); ++anIt) + { + this->ComboBox_RegionContext->addItem(anIt->c_str()); + } + this->RegionContextComboboxPopulating = false; } //----------------------------------------------------------------------------- -bool qSlicerTerminologyNavigatorWidget::overrideSectionVisible() const +void qSlicerTerminologyNavigatorWidgetPrivate::populateRegionTable() { - Q_D(const qSlicerTerminologyNavigatorWidget); - return d->frame_TerminologyOverride->isVisible(); -} + Q_Q(qSlicerTerminologyNavigatorWidget); -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::setOverrideSectionVisible(bool visible) -{ - Q_D(qSlicerTerminologyNavigatorWidget); - d->frame_TerminologyOverride->setVisible(visible); -} + this->tableWidget_Region->clearContents(); -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::populateTerminologyComboBox() -{ - Q_D(qSlicerTerminologyNavigatorWidget); - - d->ComboBox_Terminology->clear(); - d->ComboBox_Terminology_2->clear(); - - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); - if (!logic) - { - return; - } - - d->TerminologyComboboxPopulating = true; - std::vector terminologyNames; - // Note: Currently we consider all the loaded terminology contexts, and these include the ones converted - // from the compatible color table nodes in the scene. We could instead just use the compatible color - // nodes, however, we keep this conversion for now (it could be useful for saving them, or if we decide - // to use the terminology selector again for color table terminologies). - logic->GetLoadedTerminologyNames(terminologyNames); - for (std::vector::iterator termIt=terminologyNames.begin(); termIt!=terminologyNames.end(); ++termIt) - { - d->ComboBox_Terminology->addItem(termIt->c_str()); - d->ComboBox_Terminology_2->addItem(termIt->c_str()); - } - d->TerminologyComboboxPopulating = false; -} - -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::populateCategoryTable() -{ - Q_D(qSlicerTerminologyNavigatorWidget); - - d->tableWidget_Category->clearContents(); - - if (d->CurrentTerminologyName.isEmpty()) + if (this->CurrentRegionContextName.isEmpty()) { - d->tableWidget_Category->setRowCount(0); + this->tableWidget_Region->setRowCount(0); return; } - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); + vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic(); if (!logic) { qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; return; } - // Get category names containing the search string. If no search string then add every category - std::vector categories; - logic->FindCategoriesInTerminology( - d->CurrentTerminologyName.toUtf8().constData(), categories, d->SearchBox_Category->text().toUtf8().constData() ); + // Get region names containing the search string. If no search string then add every region + std::vector regions; + logic->FindRegionsInRegionContext( + this->CurrentRegionContextName.toUtf8().constData(), + regions, this->SearchBox_Region->text().toUtf8().constData()); + + this->tableWidget_Region->setRowCount(regions.size() + 1); // +1 for the "None" item - QTableWidgetItem* selectedItem = nullptr; - d->tableWidget_Category->setRowCount(categories.size()); int index = 0; + + // Add "None" item + QTableWidgetItem* noneRegionItem = new QTableWidgetItem(this->NoneItemName); + this->tableWidget_Region->setItem(index++, 0, noneRegionItem); + QTableWidgetItem* selectedItem = noneRegionItem; + std::vector::iterator idIt; - for (idIt=categories.begin(); idIt!=categories.end(); ++idIt, ++index) + for (idIt = regions.begin(); idIt != regions.end(); ++idIt, ++index) { - vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedCategoryId = (*idIt); - QString addedCategoryName(addedCategoryId.CodeMeaning.c_str()); - QTableWidgetItem* addedCategoryItem = new QTableWidgetItem(addedCategoryName); - addedCategoryItem->setData(CodingSchemeDesignatorRole, QString(addedCategoryId.CodingSchemeDesignator.c_str())); - addedCategoryItem->setData(CodeValueRole, QString(addedCategoryId.CodeValue.c_str())); - d->tableWidget_Category->setItem(index, 0, addedCategoryItem); - - if ( d->CurrentCategoryObject->GetCodingSchemeDesignator() && !addedCategoryId.CodingSchemeDesignator.compare(d->CurrentCategoryObject->GetCodingSchemeDesignator()) - && d->CurrentCategoryObject->GetCodeValue() && !addedCategoryId.CodeValue.compare(d->CurrentCategoryObject->GetCodeValue()) ) + vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedRegionId = (*idIt); + QString addedRegionName(addedRegionId.CodeMeaning.c_str()); + QTableWidgetItem* addedRegionItem = new QTableWidgetItem(addedRegionName); + addedRegionItem->setData(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole, QString(addedRegionId.CodingSchemeDesignator.c_str())); + addedRegionItem->setData(qSlicerTerminologyNavigatorWidget::CodeValueRole, QString(addedRegionId.CodeValue.c_str())); + this->tableWidget_Region->setItem(index, 0, addedRegionItem); + + if (this->CurrentRegionObject->GetCodingSchemeDesignator() + && !addedRegionId.CodingSchemeDesignator.compare(this->CurrentRegionObject->GetCodingSchemeDesignator()) + && this->CurrentRegionObject->GetCodeValue() + && !addedRegionId.CodeValue.compare(this->CurrentRegionObject->GetCodeValue())) { - selectedItem = addedCategoryItem; + selectedItem = addedRegionItem; } } - // Select category if selection was valid and item shows up in search + // Select region if selection was valid and item shows up in search if (selectedItem) { - d->tableWidget_Category->setCurrentItem(selectedItem); - } - // Otherwise select all - else - { - d->tableWidget_Category->selectAll(); + this->tableWidget_Region->setCurrentItem(selectedItem); } } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::populateTypeTable() +void qSlicerTerminologyNavigatorWidgetPrivate::populateRegionModifierComboBox() { - Q_D(qSlicerTerminologyNavigatorWidget); + Q_Q(qSlicerTerminologyNavigatorWidget); - d->tableWidget_Type->clearContents(); + this->ComboBox_RegionModifier->clear(); - // Collect selected categories. Use current category if selected list is empty and current is valid - // (selection happens from UI, current is set when setting from outside using setTerminologyEntry) - QList selectedCategories; - foreach (vtkSmartPointer category, d->SelectedCategoryObjects) - { - selectedCategories << category.GetPointer(); - } - if (!selectedCategories.count() && d->CurrentCategoryObject && d->CurrentCategoryObject->GetCodeValue()) + if (this->CurrentRegionContextName.isEmpty() || !this->CurrentRegionObject || !this->CurrentRegionObject->GetCodeValue()) { - selectedCategories << d->CurrentCategoryObject; + this->ComboBox_RegionModifier->setEnabled(false); + return; } - - // Empty table only contains none item - if (d->CurrentTerminologyName.isEmpty() || selectedCategories.count() == 0) + // If current region has no modifiers then leave it empty and disable + if (!this->CurrentRegionObject->GetHasModifiers()) { - d->tableWidget_Type->setRowCount(1); - QTableWidgetItem* noneItem = new QTableWidgetItem(d->NoneItemName); - d->tableWidget_Type->setItem(0, 0, noneItem); - d->tableWidget_Type->setCurrentItem(noneItem); + this->ComboBox_RegionModifier->setEnabled(false); return; } - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); + vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic(); if (!logic) { qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; return; } - // Get types in selected categories containing the search string. If no search string then add every type - struct TypeInfo - { - vtkSlicerTerminologiesModuleLogic::CodeIdentifier id; - vtkSlicerTerminologyCategory* category; - QColor color; - }; - std::vector types; - std::string searchTerm(d->SearchBox_Type->text().toUtf8().constData()); - std::vector>::iterator typeObjIt; - std::set existingTypesSchemeValue; - foreach (vtkSlicerTerminologyCategory* category, selectedCategories) - { - std::vector typesInCategory; - std::vector> typesObjectsInCategory; - vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId = vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(category); + // Get region modifier names + std::vector regionModifiers; + logic->GetRegionModifiersInRegion( + this->CurrentRegionContextName.toUtf8().constData(), + vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(this->CurrentRegionObject), + regionModifiers); - logic->FindTypesInTerminologyCategory( - d->CurrentTerminologyName.toUtf8().constData(), - vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(category), - typesInCategory, searchTerm, &typesObjectsInCategory); + // Add "none" item + this->ComboBox_RegionModifier->addItem(qSlicerTerminologyNavigatorWidget::tr("No region modifier")); + int selectedIndex = 0; + int index = 1; // "none" item is 0, start adding items from 1 - std::vector::iterator idIt; - for (idIt=typesInCategory.begin(), typeObjIt=typesObjectsInCategory.begin(); idIt!=typesInCategory.end(); ++idIt, ++typeObjIt) - { - // Determine if type already exists in list - std::string typesSchemeValue = idIt->CodeValue + idIt->CodingSchemeDesignator; - if (existingTypesSchemeValue.find(typesSchemeValue) != existingTypesSchemeValue.end()) - { - // duplicate - continue; - } + std::vector::iterator idIt; + for (idIt = regionModifiers.begin(); idIt != regionModifiers.end(); ++idIt, ++index) + { + vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedRegionModifierId = (*idIt); + QString addedRegionModifierName(addedRegionModifierId.CodeMeaning.c_str()); - // Add type - TypeInfo typeInfo; - typeInfo.id = *idIt; - typeInfo.category = category; - typeInfo.color = d->recommendedColorForType(d->CurrentTerminologyName.toUtf8().constData(), category, *typeObjIt); - types.push_back(typeInfo); + QMap userData; + userData[QString::number(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole)] = QString(addedRegionModifierId.CodingSchemeDesignator.c_str()); + userData[QString::number(qSlicerTerminologyNavigatorWidget::CodeValueRole)] = QString(addedRegionModifierId.CodeValue.c_str()); + this->ComboBox_RegionModifier->addItem(addedRegionModifierName, QVariant(userData)); - // Store type-category relationship - existingTypesSchemeValue.insert(typesSchemeValue); + if (!addedRegionModifierName.compare(this->CurrentRegionModifierObject->GetCodeMeaning())) + { + selectedIndex = index; } - } - QTableWidgetItem* selectedItem = nullptr; - - // Show none item only if search term is empty (if user is searching then they want an actual type) - int noneTypeExists = 0; - QTableWidgetItem* noneItem = nullptr; - if (searchTerm.empty()) - { - noneTypeExists = 1; - noneItem = new QTableWidgetItem(d->NoneItemName); - d->tableWidget_Type->setRowCount(types.size() + noneTypeExists); - d->tableWidget_Type->setItem(0, 0, noneItem); - } - else + // Select modifier if selection was valid + if (selectedIndex != -1) { - d->tableWidget_Type->setRowCount(types.size()); + this->ComboBox_RegionModifier->setCurrentIndex(selectedIndex); } +} - // Add type items to table - int typeIndex = 0; - vtkNew typeObject; - std::vector::iterator typeIt; - for (typeIt=types.begin(); typeIt!=types.end(); ++typeIt, ++typeIndex) - { - const vtkSlicerTerminologiesModuleLogic::CodeIdentifier &addedTypeId = typeIt->id; - QString addedTypeName(addedTypeId.CodeMeaning.c_str()); - QTableWidgetItem* addedTypeItem = new QTableWidgetItem(addedTypeName); - addedTypeItem->setData(CodingSchemeDesignatorRole, QString(addedTypeId.CodingSchemeDesignator.c_str())); - addedTypeItem->setData(CodeValueRole, QString(addedTypeId.CodeValue.c_str())); - // Reference containing category so that it can be set when type is selected - vtkSlicerTerminologyCategory* category = typeIt->category; - addedTypeItem->setData(CategoryCodingSchemeDesignatorRole, QString(category->GetCodingSchemeDesignator())); - addedTypeItem->setData(CategoryCodeValueRole, QString(category->GetCodeValue())); - addedTypeItem->setData(CategoryCodeMeaningRole, QString(category->GetCodeMeaning())); - QString tooltip = QString("Category: %1 (anatomy:%2)").arg(category->GetCodeMeaning()).arg( - (category->GetShowAnatomy() ? "available" : "N/A") ); - addedTypeItem->setToolTip(tooltip); - - if (typeIt->color.isValid()) - { - addedTypeItem->setData(Qt::DecorationRole, typeIt->color); - } - - // Insert type item - d->tableWidget_Type->setItem(typeIndex + noneTypeExists, 0, addedTypeItem); +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidgetPrivate::setCurrentRegionContext(QString contextName) +{ + Q_Q(qSlicerTerminologyNavigatorWidget); - if ( d->CurrentTypeObject->GetCodingSchemeDesignator() && !addedTypeId.CodingSchemeDesignator.compare(d->CurrentTypeObject->GetCodingSchemeDesignator()) - && d->CurrentTypeObject->GetCodeValue() && !addedTypeId.CodeValue.compare(d->CurrentTypeObject->GetCodeValue()) ) - { - selectedItem = addedTypeItem; - } - } + // Reset current region and region modifier + this->resetCurrentRegion(); + this->resetCurrentRegionModifier(); - if (selectedItem) + // Set current region context + this->CurrentRegionContextName = contextName; + if (contextName.isEmpty()) { - // Select type if selection was valid and item shows up in search - d->tableWidget_Type->setCurrentItem(selectedItem); + return; } - else if (noneItem) + + // Populate region table and reset region modifier combobox + this->populateRegionTable(); + this->populateRegionModifierComboBox(); + + // Only enable region table if there are items in it + if (this->tableWidget_Region->rowCount() == 0) { - // Select none item if it exists (no search and no selected item specified) - d->tableWidget_Type->setCurrentItem(noneItem); + this->tableWidget_Region->setEnabled(false); + if (this->SearchBox_Region->text().isEmpty()) + { + // Table might be empty because of a search + this->SearchBox_Region->setEnabled(false); + } + this->ComboBox_RegionModifier->setEnabled(false); } - else + else if (this->CurrentCategoryObject->GetShowAnatomy()) { - // Select first type otherwise - d->tableWidget_Type->setCurrentCell(0,0); + this->tableWidget_Region->setEnabled(true); + this->SearchBox_Region->setEnabled(true); } } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::populateTypeModifierComboBox() +bool qSlicerTerminologyNavigatorWidgetPrivate::setCurrentRegion(vtkSlicerTerminologyType* region) { - Q_D(qSlicerTerminologyNavigatorWidget); + Q_Q(qSlicerTerminologyNavigatorWidget); - d->ComboBox_TypeModifier->clear(); + // Reset current region modifier + this->resetCurrentRegionModifier(); - if (d->CurrentTerminologyName.isEmpty() || !d->CurrentCategoryObject || !d->CurrentTypeObject || !d->CurrentTypeObject->GetCodeValue()) + if (!region) { - d->ComboBox_TypeModifier->setEnabled(false); - return; + this->resetCurrentRegion(); + qCritical() << Q_FUNC_INFO << ": Invalid region object set"; + return false; } - // If current type has no modifiers then leave it empty and disable - if (!d->CurrentTypeObject->GetHasModifiers()) + + // Ignore selection if current category does not support anatomy (possible due to multi-selection) + if (!this->CurrentCategoryObject) { - d->ComboBox_TypeModifier->setEnabled(false); - return; + this->resetCurrentRegion(); + qCritical() << Q_FUNC_INFO << ": Missing current category"; + return false; } - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); - if (!logic) + // Update state of anatomy controls based on the category of the selected type + // (the controls may have been enabled because some of the selected categories supported anatomy) + this->ComboBox_RegionContext->setEnabled(this->CurrentCategoryObject->GetShowAnatomy()); + this->tableWidget_Region->setEnabled(this->CurrentCategoryObject->GetShowAnatomy()); + this->SearchBox_Region->setEnabled(this->CurrentCategoryObject->GetShowAnatomy()); + this->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection + + // Reject region selection if current type's category does not support anatomy + if (!this->CurrentCategoryObject->GetShowAnatomy()) { - qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; - return; + this->resetCurrentRegion(); + return false; } - int selectedIndex = 0; + // Set current region + this->CurrentRegionObject->Copy(region); - // Add none item so that no modifier can be selected even if there are options - d->ComboBox_TypeModifier->addItem(tr("No type modifier")); + // Populate region modifier combobox + this->populateRegionModifierComboBox(); - // Get type modifier names - std::vector typeModifiers; - logic->GetTypeModifiersInTerminologyType( - d->CurrentTerminologyName.toUtf8().constData(), - vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(d->CurrentCategoryObject), - vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(d->CurrentTypeObject), - typeModifiers ); + // Only enable region modifier combobox if there are items in it + this->ComboBox_RegionModifier->setEnabled(this->ComboBox_RegionModifier->count()); - int index = 1; // None modifier has index 0 - std::vector::iterator idIt; - for (idIt=typeModifiers.begin(); idIt!=typeModifiers.end(); ++idIt, ++index) + // "None" is selected + if (!region->GetCodeValue()) { - vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedTypeModifierId = (*idIt); - QString addedTypeModifierName(addedTypeModifierId.CodeMeaning.c_str()); - - QMap userData; - userData[QString::number(CodingSchemeDesignatorRole)] = QString(addedTypeModifierId.CodingSchemeDesignator.c_str()); - userData[QString::number(CodeValueRole)] = QString(addedTypeModifierId.CodeValue.c_str()); - d->ComboBox_TypeModifier->addItem(addedTypeModifierName, QVariant(userData)); - - if (selectedIndex == -1 && !addedTypeModifierName.compare(d->CurrentTypeModifierObject->GetCodeMeaning())) - { - selectedIndex = index; - } + this->resetCurrentRegion(); + this->tableWidget_Region->blockSignals(true); + this->tableWidget_Region->setCurrentCell(0, 0); + this->tableWidget_Region->blockSignals(false); + return false; } - // Select modifier if selection was valid - if (selectedIndex != -1) + // Select region if found + QTableWidgetItem* regionItem = this->findTableWidgetItemForType(this->tableWidget_Region, region); + if (regionItem) { - d->ComboBox_TypeModifier->setCurrentIndex(selectedIndex); + this->tableWidget_Region->blockSignals(true); + this->tableWidget_Region->setCurrentItem(regionItem); + this->tableWidget_Region->blockSignals(false); } + return regionItem; // Return true if region found and selected } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::setCurrentTerminology(QString terminologyName) +bool qSlicerTerminologyNavigatorWidgetPrivate::setCurrentRegionModifier(vtkSlicerTerminologyType* modifier) { - Q_D(qSlicerTerminologyNavigatorWidget); - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); - if (!logic) - { - qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; - return; - } + Q_Q(qSlicerTerminologyNavigatorWidget); - // If no change then nothing to do - if (d->CurrentTerminologyName == terminologyName) + if (!modifier) { - return; + this->resetCurrentRegionModifier(); + qCritical() << Q_FUNC_INFO << ": Invalid region modifier object set"; + return false; } - // Reset current category, type, and type modifier - d->resetCurrentCategory(); - d->resetCurrentType(); - d->resetCurrentTypeModifier(); + // Set current type modifier + this->CurrentRegionModifierObject->Copy(modifier); - // Set current terminology - d->CurrentTerminologyName = terminologyName; - if (terminologyName.isEmpty()) + // Select modifier if found + int modifierIndex = this->findComboBoxIndexForModifier(this->ComboBox_RegionModifier, modifier); + if (modifierIndex != -1) { - return; + this->ComboBox_RegionModifier->blockSignals(true); + this->ComboBox_RegionModifier->setCurrentIndex(modifierIndex); + this->ComboBox_RegionModifier->blockSignals(false); } + return (modifierIndex != -1); +} +//----------------------------------------------------------------------------- +// qSlicerTerminologyNavigatorWidget methods - /*TODO: - const char* colorTableNodeID = logic->IsTerminologyColorTable(terminologyName.toUtf8().constData()); - bool terminologyIsColorTable = (colorTableNodeID != nullptr); - */ - bool terminologyIsColorTable = false; - std::string colorTableNodeID; - - - if (!terminologyIsColorTable) - { - // Selected terminology is a loaded terminology JSON - // Populate category table, and reset type table and type modifier combobox - this->populateCategoryTable(); - this->populateTypeTable(); - this->populateTypeModifierComboBox(); +//----------------------------------------------------------------------------- +qSlicerTerminologyNavigatorWidget::qSlicerTerminologyNavigatorWidget(QWidget* _parent) + : qMRMLWidget(_parent) + , d_ptr(new qSlicerTerminologyNavigatorWidgetPrivate(*this)) +{ + Q_D(qSlicerTerminologyNavigatorWidget); + d->init(); - // Only enable category table if there are items in it - if (d->tableWidget_Category->rowCount() == 0) - { - d->tableWidget_Category->setEnabled(!d->SearchBox_Type->text().isEmpty()); // Might be empty because of a search - //d->tableWidget_Type->setEnabled(false); - d->SearchBox_Type->setEnabled(false); - d->ComboBox_TypeModifier->setEnabled(false); - } - else - { - d->tableWidget_Category->setEnabled(true); - d->SearchBox_Category->setEnabled(true); - } - } - else + // Connect logic modified event (cannot call QVTK from private implementation) + vtkSlicerTerminologiesModuleLogic* logic = qSlicerTerminologyNavigatorWidgetPrivate::terminologyLogic(); + if (logic) { - // Selected terminology was converted from compatible color table - vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast( - logic->GetMRMLScene()->GetNodeByID(colorTableNodeID)); - d->ColorTableView->setMRMLColorNode(colorNode); + qvtkConnect(logic, vtkCommand::ModifiedEvent, this, SLOT(onLogicModified())); } - - // Show widgets corresponding to selection - d->frame_TerminologyEntry->setVisible(!terminologyIsColorTable); - d->frame_ColorTable->setVisible(terminologyIsColorTable); - - // Selection is valid if there is a valid type object - emit selectionValidityChanged(d->CurrentTypeObject && d->CurrentTypeObject->GetCodeValue()); } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onTerminologySelectionChanged(int index) +qSlicerTerminologyNavigatorWidget::~qSlicerTerminologyNavigatorWidget() { Q_D(qSlicerTerminologyNavigatorWidget); - - // Make selection in the other combobox as well - ctkComboBox* visibleComboBox = (d->ComboBox_Terminology->isVisible() ? - d->ComboBox_Terminology : d->ComboBox_Terminology_2); - ctkComboBox* invisibleComboBox = (!d->ComboBox_Terminology->isVisible() ? - d->ComboBox_Terminology : d->ComboBox_Terminology_2); - QSignalBlocker blocker(invisibleComboBox); - invisibleComboBox->setCurrentIndex(visibleComboBox->currentIndex()); - - // Set current terminology - QString terminologyName = visibleComboBox->itemText(index); - this->setCurrentTerminology(terminologyName); - - // Save last used terminology context in application settings - if (!d->TerminologyComboboxPopulating) + if (qSlicerApplication::application()) { - QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings(); - QStringList lastTerminologyContextNames = settings->value("Terminology/LastTerminologyContexts").toStringList(); - if (!lastTerminologyContextNames.isEmpty()) - { - if (lastTerminologyContextNames.size() == 1 && lastTerminologyContextNames[0] == terminologyName) - { - // Nothing to change - return; - } - // Remove the terminology name from the list so that there are no duplicate entries in the list - lastTerminologyContextNames.removeOne(terminologyName); - // Prepend terminology name to the list so that the last used terminology is first - lastTerminologyContextNames.insert(0, terminologyName); - } - else - { - lastTerminologyContextNames.push_back(terminologyName); - } - settings->setValue("Terminology/LastTerminologyContexts", lastTerminologyContextNames); + QSettings* settings = qSlicerApplication::application()->userSettings(); + settings->setValue("Terminology/ShowCategorySelector", d->CategoryExpandButton->isChecked()); + settings->setValue("Terminology/ShowRegionSelector", d->RegionExpandButton->isChecked()); } } //----------------------------------------------------------------------------- -bool qSlicerTerminologyNavigatorWidget::setCurrentCategory(vtkSlicerTerminologyCategory* category) +void qSlicerTerminologyNavigatorWidget::terminologyInfo(TerminologyInfoBundle &terminologyInfo) { Q_D(qSlicerTerminologyNavigatorWidget); - - // Reset current type and type modifier - d->resetCurrentType(); - d->resetCurrentTypeModifier(); - // Reset region information as well - d->resetCurrentRegion(); - d->resetCurrentRegionModifier(); - - if (!category) - { - d->resetCurrentCategory(); - qCritical() << Q_FUNC_INFO << ": Invalid category object set"; - return false; - } - - // Set current category - d->CurrentCategoryObject->Copy(category); - - // Populate type table, and reset type modifier combobox and region widgets - this->populateTypeTable(); - this->populateTypeModifierComboBox(); - d->tableWidget_Region->setCurrentItem(nullptr); - this->populateRegionModifierComboBox(); - - // Update widget UI from current category - this->updateWidgetFromCurrentCategory(); - - // Selection is invalid until type is selected - emit selectionValidityChanged(false); - - // Select category if found - QTableWidgetItem* categoryItem = d->findTableWidgetItemForCategory(category); - return (categoryItem != nullptr); // Return true if category found and selected + this->terminologyEntry(terminologyInfo.GetTerminologyEntry()); + terminologyInfo.Name = d->lineEdit_Name->text(); + terminologyInfo.NameAutoGenerated = d->NameAutoGenerated; + terminologyInfo.Color = d->ColorPickerButton_RecommendedRGB->color(); + terminologyInfo.ColorAutoGenerated = d->ColorAutoGenerated; } +// public method to set terminology in GUI //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::updateWidgetFromCurrentCategory() +void qSlicerTerminologyNavigatorWidget::setTerminologyInfo(TerminologyInfoBundle &terminologyInfo) { Q_D(qSlicerTerminologyNavigatorWidget); - // Only enable type table if there are items in it - if (d->tableWidget_Type->rowCount() == 1) // None item always present + // Set terminology entry + if (!this->setTerminologyEntry(terminologyInfo.GetTerminologyEntry())) { - d->ComboBox_TypeModifier->setEnabled(false); + qWarning() << Q_FUNC_INFO << ": Failed to set terminology entry from given terminology info bundle"; + } + bool noneType = (d->CurrentTypeObject->GetCodeValue() == nullptr); + + // Set name + if (terminologyInfo.Name.isEmpty()) + { + // If given name is empty, then generate it from terminology (auto-generate flag will be true) + d->setNameFromCurrentTerminology(); } else { - //d->tableWidget_Type->setEnabled(true); - d->SearchBox_Type->setEnabled(true); + d->lineEdit_Name->blockSignals(true); // Only call callback function if user changes from UI + d->lineEdit_Name->setText(terminologyInfo.Name); + d->lineEdit_Name->blockSignals(false); + + d->NameAutoGenerated = terminologyInfo.NameAutoGenerated; + d->pushButton_ResetName->setEnabled(!d->NameAutoGenerated && !noneType); } - // Enable region controls if related flag is on - d->ComboBox_RegionContext->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); - d->tableWidget_Region->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); - d->SearchBox_Region->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); - d->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection - if (this->regionSectionVisible()) + // Store generated color. It is used when the selected terminology contains no recommended color + if (terminologyInfo.GeneratedColor.isValid()) { - // Always enable expand button if panel is visible - d->RegionExpandButton->setEnabled(true); + d->GeneratedColor = terminologyInfo.GeneratedColor; + } + + // Set color + if (!terminologyInfo.Color.isValid()) + { + // If given color is invalid, then get it from terminology (auto-generate flag will be true) + d->setRecommendedColorFromCurrentTerminology(); } else { - // Only enable expand button if region is enabled in selected category - d->RegionExpandButton->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); - // Blink button if region is enabled to let the user know there are additional options available - if (d->CurrentCategoryObject->GetShowAnatomy()) - { - d->RegionExpandButton->setDown(true); - QTimer::singleShot(50, this, SLOT(regionExpandButtonUp())); - QTimer::singleShot(100, this, SLOT(regionExpandButtonDown())); - QTimer::singleShot(150, this, SLOT(regionExpandButtonUp())); - QTimer::singleShot(200, this, SLOT(regionExpandButtonDown())); - QTimer::singleShot(250, this, SLOT(regionExpandButtonUp())); - } + d->ColorPickerButton_RecommendedRGB->blockSignals(true); // Only call callback function if user changes from UI + d->ColorPickerButton_RecommendedRGB->setColor(terminologyInfo.Color); + d->ColorPickerButton_RecommendedRGB->blockSignals(false); + + d->ColorAutoGenerated = terminologyInfo.ColorAutoGenerated; + + bool enableResetColor = !d->ColorAutoGenerated + || (d->ColorPickerButton_RecommendedRGB->color() != d->terminologyRecommendedColor()); + d->pushButton_ResetColor->setEnabled(enableResetColor && !noneType); } } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onCategorySelectionChanged() +bool qSlicerTerminologyNavigatorWidget::terminologyEntry(vtkSlicerTerminologyEntry* entry) { Q_D(qSlicerTerminologyNavigatorWidget); - QList selectedItems = d->tableWidget_Category->selectedItems(); - - if (selectedItems.count() == 0) + if (!entry) { - // Happens when clearing the table - return; + qCritical() << Q_FUNC_INFO << ": Invalid terminology entry object"; + return false; } - - // Reset current category because it will be set when the type is selected - d->resetCurrentCategory(); - // Reset current type and type modifier - d->resetCurrentType(); - d->resetCurrentTypeModifier(); - // Reset region information as well - d->resetCurrentRegion(); - d->resetCurrentRegionModifier(); - - // Get current category object - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); - if (!logic) + if (!entry->GetCategoryObject() || !entry->GetTypeObject() || !entry->GetTypeModifierObject() + || !entry->GetRegionObject() || !entry->GetRegionModifierObject() ) { - qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; - return; + qCritical() << Q_FUNC_INFO << ": Invalid terminology entry given"; + // Invalidate whole terminology entry + entry->SetTerminologyContextName(nullptr); + entry->SetRegionContextName(nullptr); + return false; } - bool showAnatomyOnInAnyCategories = false; - - d->SelectedCategoryObjects.clear(); - foreach (QTableWidgetItem* currentItem, selectedItems) + // Terminology name + if (d->CurrentTerminologyName.isEmpty()) { - vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId( - currentItem->data(CodingSchemeDesignatorRole).toString().toUtf8().constData(), - currentItem->data(CodeValueRole).toString().toUtf8().constData(), - currentItem->text().toUtf8().constData() ); - vtkSmartPointer category = vtkSmartPointer::New(); - if (!logic->GetCategoryInTerminology( - d->CurrentTerminologyName.toUtf8().constData(), categoryId, category) ) - { - qCritical() << Q_FUNC_INFO << ": Failed to find category '" << currentItem->text(); - continue; - } - - showAnatomyOnInAnyCategories |= category->GetShowAnatomy(); + qCritical() << Q_FUNC_INFO << ": No terminology selected"; + return false; + } + entry->SetTerminologyContextName(d->CurrentTerminologyName.toUtf8().constData()); - d->SelectedCategoryObjects << category; + // Terminology category + if (!d->CurrentCategoryObject) + { + qCritical() << Q_FUNC_INFO << ": No terminology category selected"; + return false; } + entry->GetCategoryObject()->Copy(d->CurrentCategoryObject); - // Populate type table, and reset type modifier combobox and region widgets - this->populateTypeTable(); - this->populateTypeModifierComboBox(); - this->populateRegionModifierComboBox(); + // Terminology type + if (!d->CurrentTypeObject) + { + qCritical() << Q_FUNC_INFO << ": No terminology type selected"; + return false; + } + entry->GetTypeObject()->Copy(d->CurrentTypeObject); - // Only enable type table if there are items in it - if (d->tableWidget_Type->rowCount() == 1) // None item always present + // Terminology type modifier + if (d->CurrentTypeModifierObject) { - d->ComboBox_TypeModifier->setEnabled(false); + entry->GetTypeModifierObject()->Copy(d->CurrentTypeModifierObject); } else { - d->SearchBox_Type->setEnabled(true); + entry->GetTypeModifierObject()->Initialize(); } - // Enable region controls if related flag is on - d->ComboBox_RegionContext->setEnabled(showAnatomyOnInAnyCategories); - d->tableWidget_Region->setEnabled(showAnatomyOnInAnyCategories); - d->SearchBox_Region->setEnabled(showAnatomyOnInAnyCategories); - d->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection - - // Selection is invalid until type is selected - emit selectionValidityChanged(false); + // Region context name + if (!d->CurrentRegionContextName.isEmpty()) + { + entry->SetRegionContextName(d->CurrentRegionContextName.toUtf8().constData()); + } - // Generate name based on selection (empty name in this case) if not custom - if (d->NameAutoGenerated) + // Region + if (d->CurrentCategoryObject->GetShowAnatomy() && d->CurrentRegionObject) { - d->setNameFromCurrentTerminology(); + entry->GetRegionObject()->Copy(d->CurrentRegionObject); + } + else + { + entry->GetRegionObject()->Initialize(); } -} -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onSelectAllCategoriesButtonClicked() -{ - Q_D(qSlicerTerminologyNavigatorWidget); + // Region modifier + if (d->CurrentCategoryObject->GetShowAnatomy() && d->CurrentRegionModifierObject) + { + entry->GetRegionModifierObject()->Copy(d->CurrentRegionModifierObject); + } + else + { + entry->GetRegionModifierObject()->Initialize(); + } - d->tableWidget_Category->selectAll(); + return true; } //----------------------------------------------------------------------------- -bool qSlicerTerminologyNavigatorWidget::setCurrentType(vtkSlicerTerminologyType* type) +bool qSlicerTerminologyNavigatorWidgetPrivate::findTerminology( + vtkSlicerTerminologyEntry* entry, QString& terminologyNameToSelect, QString& colorNodeIDToSelect, int& colorIndexInColorTable) { - Q_D(qSlicerTerminologyNavigatorWidget); - - // Reset current type modifier - d->resetCurrentTypeModifier(); - - // Null type means None selection - if (!type) + Q_Q(qSlicerTerminologyNavigatorWidget); + vtkSlicerTerminologiesModuleLogic* logic = this->terminologyLogic(); + if (!logic) { - d->resetCurrentType(); - d->tableWidget_Type->blockSignals(true); - d->tableWidget_Type->setCurrentCell(0,0); - d->tableWidget_Type->blockSignals(false); - emit selectionValidityChanged(true); // None selection is valid too + qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; return false; } - - // Set current type - d->CurrentTypeObject->Copy(type); - - // Populate type modifier combobox - this->populateTypeModifierComboBox(); - - // Only enable type modifier combobox if there are items in it - d->ComboBox_TypeModifier->setEnabled(d->ComboBox_TypeModifier->count()); - - // With valid type selected, terminology selection becomes also valid - emit selectionValidityChanged(true); - - // Select type if found - QTableWidgetItem* typeItem = d->findTableWidgetItemForType(d->tableWidget_Type, type); - if (typeItem) + if (this->ComboBox_Terminology->count() < 1) { - d->tableWidget_Type->blockSignals(true); - d->tableWidget_Type->setCurrentItem(typeItem); - d->tableWidget_Type->blockSignals(false); + return false; } - return typeItem; // Return true if type found and selected -} + // Select the first terminology item by default + terminologyNameToSelect = this->ComboBox_Terminology->itemText(0); + colorNodeIDToSelect = this->ComboBox_Terminology->itemData(0).toString(); + colorIndexInColorTable = -1; -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onTypeSelected(QTableWidgetItem* currentItem, QTableWidgetItem* previousItem) -{ - Q_UNUSED(previousItem); - Q_D(qSlicerTerminologyNavigatorWidget); - - if (!currentItem) + if (!entry->GetTerminologyContextName() && + (!entry->GetCategoryObject() || !entry->GetCategoryObject()->GetCodeValue())) { - d->resetCurrentType(); - d->resetCurrentTypeModifier(); - return; + // empty item selected, use the default (first terminology) + return true; } - // Reset terminology elements if none item is selected - if (!currentItem->text().compare(d->NoneItemName)) + // Get list of last terminology contexts selected by the user from application settings + std::vector preferredTerminologyNames; + QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings(); + if (settings->contains("Terminology/LastTerminologyContexts")) { - // Do not reset category because it invalidates entry when setting terminology - // programmatically using setTerminologyEntry - d->resetCurrentType(); - d->resetCurrentTypeModifier(); - d->resetCurrentRegion(); - d->resetCurrentRegionModifier(); - return; + QStringList lastTerminologyContextNames = settings->value("Terminology/LastTerminologyContexts").toStringList(); + for (auto& name : lastTerminologyContextNames) + { + preferredTerminologyNames.push_back(name.toUtf8().constData()); + } } - - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); - if (!logic) + std::string categoryScheme; + std::string categoryValue; + vtkSlicerTerminologyCategory* categoryObject = entry->GetCategoryObject(); + if (categoryObject) { - qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; - return; + categoryScheme = categoryObject->GetCodingSchemeDesignator(); + categoryValue = categoryObject->GetCodeValue(); } - - // Get category object for selected type object - vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId( - currentItem->data(CategoryCodingSchemeDesignatorRole).toString().toUtf8().constData(), - currentItem->data(CategoryCodeValueRole).toString().toUtf8().constData(), - currentItem->data(CategoryCodeMeaningRole).toString().toUtf8().constData() ); - vtkSmartPointer category = vtkSmartPointer::New(); - if (!logic->GetCategoryInTerminology( - d->CurrentTerminologyName.toUtf8().constData(), - categoryId, category) ) + std::string typeScheme; + std::string typeValue; + vtkSlicerTerminologyType* typeObject = entry->GetTypeObject(); + if (typeObject) { - qCritical() << Q_FUNC_INFO << ": Failed to find category '" << categoryId.CodeMeaning.c_str(); - return; + typeScheme = typeObject->GetCodingSchemeDesignator(); + typeValue = typeObject->GetCodeValue(); } - - // Get current type object - vtkSlicerTerminologiesModuleLogic::CodeIdentifier typeId( - currentItem->data(CodingSchemeDesignatorRole).toString().toUtf8().constData(), - currentItem->data(CodeValueRole).toString().toUtf8().constData(), - currentItem->text().toUtf8().constData() ); - vtkSmartPointer type = vtkSmartPointer::New(); - if (!logic->GetTypeInTerminologyCategory( - d->CurrentTerminologyName.toUtf8().constData(), categoryId, typeId, type) ) + std::string typeModifierScheme; + std::string typeModifierValue; + vtkSlicerTerminologyType* typeModifierObject = entry->GetTypeModifierObject(); + if (typeModifierObject && typeModifierObject->GetCodingSchemeDesignator() && typeModifierObject->GetCodeValue()) { - qCritical() << Q_FUNC_INFO << ": Failed to find type '" << currentItem->text(); - return; + typeModifierScheme = typeModifierObject->GetCodingSchemeDesignator(); + typeModifierValue = typeModifierObject->GetCodeValue(); + } + vtkSlicerTerminologyType* regionObject = entry->GetRegionObject(); + std::string regionScheme; + std::string regionValue; + if (regionObject && regionObject->GetCodingSchemeDesignator() && regionObject->GetCodeValue()) + { + regionScheme = regionObject->GetCodingSchemeDesignator(); + regionValue = regionObject->GetCodeValue(); + } + vtkSlicerTerminologyType* regionModifierObject = entry->GetRegionModifierObject(); + std::string regionModifierScheme; + std::string regionModifierValue; + if (regionModifierObject && regionModifierObject->GetCodingSchemeDesignator() && regionModifierObject->GetCodeValue()) + { + regionModifierScheme = regionModifierObject->GetCodingSchemeDesignator(); + regionModifierValue = regionModifierObject->GetCodeValue(); } - // Set current category object for selected type - d->CurrentCategoryObject->Copy(category); - this->updateWidgetFromCurrentCategory(); - // Set type from item - this->setCurrentType(type); + std::vector foundColorTableNodeIds; + vtkNew foundColorIndices; + std::vector foundTerminologyNames; + foundColorTableNodeIds = logic->FindColorNodes( + categoryScheme, categoryValue, typeScheme, typeValue, typeModifierScheme, typeModifierValue, + regionScheme, regionValue, regionModifierScheme, regionModifierValue, + preferredTerminologyNames, foundColorIndices); + if (foundColorTableNodeIds.empty()) + { + foundTerminologyNames = logic->FindTerminologyNames( + categoryScheme, categoryValue, typeScheme, typeValue, typeModifierScheme, typeModifierValue, + preferredTerminologyNames); + } + if (!preferredTerminologyNames.empty() && (foundColorTableNodeIds.empty() && foundTerminologyNames.empty())) + { + // Preferred terminologies do not contain the item, try to get first terminology containing it among all loaded contexts + foundColorTableNodeIds = logic->FindColorNodes( + categoryScheme, categoryValue, typeScheme, typeValue, typeModifierScheme, typeModifierValue, + regionScheme, regionValue, regionModifierScheme, regionModifierValue, + std::vector(), foundColorIndices); - // Update state of anatomy controls based on the category of the selected type - d->ComboBox_RegionContext->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); - d->tableWidget_Region->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); - d->SearchBox_Region->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); - d->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection + foundTerminologyNames = logic->FindTerminologyNames( + categoryScheme, categoryValue, typeScheme, typeValue, typeModifierScheme, typeModifierValue, + std::vector()); + } - // Generate name based on selection if not custom - if (d->NameAutoGenerated) + if (!foundColorTableNodeIds.empty()) { - d->setNameFromCurrentTerminology(); + std::string firstColorNodeID = foundColorTableNodeIds.front(); + vtkMRMLColorNode* colorNode = vtkMRMLColorNode::SafeDownCast(logic->GetMRMLScene()->GetNodeByID(firstColorNodeID)); + if (!colorNode) + { + qCritical() << Q_FUNC_INFO << ": Failed to find color node by ID " << firstColorNodeID.c_str(); + return false; + } + terminologyNameToSelect = QString::fromStdString(colorNode->GetName() ? colorNode->GetName() : ""); + colorNodeIDToSelect = QString::fromStdString(firstColorNodeID); + if (foundColorIndices->GetNumberOfValues() > 0) + { + colorIndexInColorTable = foundColorIndices->GetValue(0); + } } - // Set recommended color to color picker if not custom - if (d->ColorAutoGenerated) + else if (!foundTerminologyNames.empty()) { - d->setRecommendedColorFromCurrentTerminology(); + terminologyNameToSelect = QString::fromStdString(foundTerminologyNames[0]); + colorNodeIDToSelect.clear(); } + return true; } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onTypeCellDoubleClicked(int row, int column) -{ - Q_UNUSED(row); - Q_UNUSED(column); - emit typeDoubleClicked(); -} - -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onColorSelected(const QItemSelection& selected, const QItemSelection& deselected) +bool qSlicerTerminologyNavigatorWidget::setTerminologyEntry(vtkSlicerTerminologyEntry* entry) { - Q_UNUSED(selected); - Q_UNUSED(deselected); Q_D(qSlicerTerminologyNavigatorWidget); - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); - if (!logic) + + if (!entry) { - qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; - return; + qCritical() << Q_FUNC_INFO << ": Invalid terminology entry object"; + return false; } - if (!d->ColorTableView->currentIndex().isValid()) + + QString terminologyName; + QString colorNodeId; + int colorIndexInColorTable = -1; + if (!d->findTerminology(entry, terminologyName, colorNodeId, colorIndexInColorTable)) { - return; + return false; } + d->setCurrentTerminology(terminologyName, colorNodeId); - // Get color node - vtkMRMLColorNode* colorNode = d->ColorTableView->mrmlColorNode(); + bool returnValue = true; + if (!colorNodeId.isEmpty()) + { + // Select from color table + d->ColorTableView->selectColorByIndex(colorIndexInColorTable); + QTimer::singleShot(0, this, SLOT(scrollToSelectedColorAfterLayout())); + } + else + { + // Select from terminology - // Get color index from selected model index - QSortFilterProxyModel* sortFilterModel = d->ColorTableView->sortFilterProxyModel(); - qMRMLColorModel* colorModel = d->ColorTableView->colorModel(); - int colorIndex = colorModel->colorFromIndex(sortFilterModel->mapToSource(d->ColorTableView->currentIndex())); + // Select category + vtkSlicerTerminologyCategory* categoryObject = entry->GetCategoryObject(); + if (!d->setCurrentCategory(categoryObject)) + { + qCritical() << Q_FUNC_INFO << ": Failed to find category with name " << (categoryObject->GetCodeMeaning() ? categoryObject->GetCodeMeaning() : "NULL"); + returnValue = false; + } - if ( colorNode->GetTerminologyCategory(colorIndex) == nullptr || colorNode->GetTerminologyCategory(colorIndex)->GetCodeMeaning() == nullptr - || colorNode->GetTerminologyType(colorIndex) == nullptr || colorNode->GetTerminologyType(colorIndex)->GetCodeMeaning() == nullptr) - { - qCritical() << Q_FUNC_INFO << ": Invalid terminology in selected color (" << colorNode->GetName() << ": " << colorIndex << ")"; - emit selectionValidityChanged(false); - return; + // Select type + vtkSlicerTerminologyType* typeObject = entry->GetTypeObject(); + if (!typeObject) + { + qCritical() << Q_FUNC_INFO << ": No type object in terminology entry"; + returnValue = false; + } + else if (!d->setCurrentType(typeObject)) + { + qCritical() << Q_FUNC_INFO << ": Failed to find type with name " << (typeObject->GetCodeMeaning() ? typeObject->GetCodeMeaning() : "NULL"); + returnValue = false; + } + + // Select type modifier + vtkSlicerTerminologyType* typeModifierObject = entry->GetTypeModifierObject(); + if (typeObject && typeObject->GetHasModifiers() && typeModifierObject && typeModifierObject->GetCodeValue()) + { + if (!d->setCurrentTypeModifier(typeModifierObject)) + { + qCritical() << Q_FUNC_INFO << ": Failed to find type modifier with name" + << (typeModifierObject->GetCodeMeaning() ? typeModifierObject->GetCodeMeaning() : "NULL"); + returnValue = false; + } + } + + // Set region context selection if category allows + if (categoryObject->GetShowAnatomy()) + { + // Select region context + QString regionContextName(entry->GetRegionContextName() ? entry->GetRegionContextName() : ""); + if (regionContextName.isEmpty()) + { + QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings(); + if (settings->contains("Terminology/LastRegionContext")) + { + QString lastRegionContextName = settings->value("Terminology/LastRegionContext").toString(); + if (!lastRegionContextName.isEmpty()) + { + regionContextName = lastRegionContextName; + } + } + } + if (!regionContextName.isEmpty()) // Optional + { + int regionContextIndex = d->ComboBox_RegionContext->findText(regionContextName); + if (regionContextIndex == -1) + { + qCritical() << Q_FUNC_INFO << ": Failed to find region context with context name " << regionContextName; + returnValue = false; + } + if (regionContextIndex != d->ComboBox_RegionContext->currentIndex()) + { + d->setCurrentRegionContext(d->ComboBox_RegionContext->itemText(regionContextIndex)); + } + d->ComboBox_RegionContext->blockSignals(true); + d->ComboBox_RegionContext->setCurrentIndex(regionContextIndex); + d->ComboBox_RegionContext->blockSignals(false); + } + + // Select region + vtkSlicerTerminologyType* regionObject = entry->GetRegionObject(); + if (regionObject) // Optional + { + d->setCurrentRegion(regionObject); + + // Select region modifier + vtkSlicerTerminologyType* regionModifierObject = entry->GetRegionModifierObject(); + if (regionObject->GetHasModifiers() && regionModifierObject) + { + d->setCurrentRegionModifier(regionModifierObject); + } + } // If region is selected + } // If showAnatomy is true + else + { + // Set region context combobox selection to the last selected context anyway, so that when + // the user changes category, the selected region context is the last selected one + QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings(); + if (settings->contains("Terminology/LastRegionContext")) + { + QString lastRegionContextName = settings->value("Terminology/LastRegionContext").toString(); + int lastRegionContextIndex = d->ComboBox_RegionContext->findText(lastRegionContextName); + if (lastRegionContextIndex >= 0) + { + d->ComboBox_RegionContext->blockSignals(true); + d->ComboBox_RegionContext->setCurrentIndex(lastRegionContextIndex); + d->ComboBox_RegionContext->blockSignals(false); + + d->setCurrentRegionContext(lastRegionContextName); + } + } + } } - // Reset current terminology information - d->resetCurrentCategory(); - d->resetCurrentType(); - d->resetCurrentTypeModifier(); - d->resetCurrentRegion(); - d->resetCurrentRegionModifier(); + return returnValue; +} - // Set category and type - d->CurrentCategoryObject->vtkCodedEntry::Copy(colorNode->GetTerminologyCategory(colorIndex)); - d->CurrentTypeObject->vtkCodedEntry::Copy(colorNode->GetTerminologyType(colorIndex)); - double color[4] = {0.0}; - colorNode->GetColor(colorIndex, color); - d->CurrentTypeObject->SetRecommendedDisplayRGBValue( - (unsigned char)(color[0] * 255.0), (unsigned char)(color[1] * 255.0), (unsigned char)(color[2] * 255.0)); - emit selectionValidityChanged(true); +//----------------------------------------------------------------------------- +QString qSlicerTerminologyNavigatorWidget::nameFromTerminology(vtkSlicerTerminologyEntry* entry) +{ + QString name; + if ( !entry->GetTypeObject() || !entry->GetTypeObject()->GetCodeValue() || + (entry->GetTypeObject()->GetHasModifiers() && !entry->GetTypeModifierObject()) ) + { + // Incomplete terminology selection, name is empty + return name; + } - // Set optional information if any - if ( colorNode->GetTerminologyTypeModifier(colorIndex) != nullptr - && colorNode->GetTerminologyTypeModifier(colorIndex)->GetCodeMeaning() != nullptr ) + // Try to set name based on '3dSlicerLabel' field in terminology entry + if ( entry->GetTypeObject()->GetSlicerLabel() + && (!entry->GetTypeObject()->GetHasModifiers() || entry->GetTypeModifierObject()->GetCodeValue() == nullptr) ) { - d->CurrentTypeModifierObject->vtkCodedEntry::Copy(colorNode->GetTerminologyTypeModifier(colorIndex)); + name = entry->GetTypeObject()->GetSlicerLabel(); } - else + else if (entry->GetTypeModifierObject()->GetSlicerLabel()) { - d->resetCurrentTypeModifier(); + name = entry->GetTypeModifierObject()->GetSlicerLabel(); + } + // Assemble from selection if there is no label + if (name.isEmpty()) + { + // Set type and modifier in name + if (entry->GetTypeObject()->GetHasModifiers() && entry->GetTypeModifierObject() && entry->GetTypeModifierObject()->GetCodeValue()) + { + //: For formatting of terminology entry with a modifier. %1 is structure name (e.g., "Kidney"), %2 is modifier (e.g., "Left") + name = tr("%1, %2") + .arg(entry->GetTypeObject()->GetCodeMeaning()) + .arg(entry->GetTypeModifierObject()->GetCodeMeaning()); + } + else + { + name = entry->GetTypeObject()->GetCodeMeaning(); + } } - if ( colorNode->GetTerminologyRegion(colorIndex) != nullptr - && colorNode->GetTerminologyRegion(colorIndex)->GetCodeMeaning() != nullptr ) + + if (entry->GetRegionObject() && entry->GetRegionObject()->GetCodeValue()) { - d->CurrentRegionObject->vtkCodedEntry::Copy(colorNode->GetTerminologyRegion(colorIndex)); + if (entry->GetRegionModifierObject() && entry->GetRegionModifierObject()->GetCodeValue()) + { + //: For formatting of terminology entry name. %1 is type name (e.g., "Mass"), %2 is region name (e.g., "Kidney"), %2 is region modifier (e.g., "Left") + name = tr("%1 in %2, %3") + .arg(name) + .arg(entry->GetRegionObject()->GetCodeMeaning()) + .arg(entry->GetRegionModifierObject()->GetCodeMeaning()); + } + else + { + //: For formatting of terminology entry name. %1 is type name (e.g., "Mass"), %2 is region name (e.g., "Liver") + name = tr("%1 in %2") + .arg(name) + .arg(entry->GetRegionObject()->GetCodeMeaning()); + } } - else + + if (name.isEmpty()) { - d->resetCurrentRegion(); + qCritical() << Q_FUNC_INFO << ": Failed to generate name"; } - if ( colorNode->GetTerminologyRegionModifier(colorIndex) != nullptr - && colorNode->GetTerminologyRegionModifier(colorIndex)->GetCodeMeaning() != nullptr ) + return name; +} + +//----------------------------------------------------------------------------- +QString qSlicerTerminologyNavigatorWidget::nameFromCurrentTerminology() +{ + Q_D(qSlicerTerminologyNavigatorWidget); + if ( !d->CurrentTypeObject || + (d->CurrentTypeObject->GetHasModifiers() && !d->CurrentTypeModifierObject) ) { - d->CurrentRegionModifierObject->vtkCodedEntry::Copy(colorNode->GetTerminologyRegionModifier(colorIndex)); + // Incomplete terminology selection, name is empty + return QString(); } - else + vtkSmartPointer terminologyEntry = vtkSmartPointer::New(); + if (!this->terminologyEntry(terminologyEntry)) { - d->resetCurrentRegionModifier(); + // Failed to get current terminology + return QString(); } - // Set name and color - d->setNameFromCurrentTerminology(); - d->setRecommendedColorFromCurrentTerminology(); -} -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onColorRowDoubleClicked(const QModelIndex &index) -{ - Q_UNUSED(index); - emit colorDoubleClicked(); + return qSlicerTerminologyNavigatorWidget::nameFromTerminology(terminologyEntry); } //----------------------------------------------------------------------------- -bool qSlicerTerminologyNavigatorWidget::setCurrentTypeModifier(vtkSlicerTerminologyType* modifier) +QColor qSlicerTerminologyNavigatorWidget::recommendedColorFromTerminology(vtkSlicerTerminologyEntry* entry) { - Q_D(qSlicerTerminologyNavigatorWidget); - - if (!modifier) + QColor color; + if (!entry || !entry->GetTypeObject()) { - d->resetCurrentTypeModifier(); - qCritical() << Q_FUNC_INFO << ": Invalid type modifier object set"; - return false; + return color; } - // Set current type modifier - d->CurrentTypeModifierObject->Copy(modifier); - - // Select modifier if found - int modifierIndex = d->findComboBoxIndexForModifier(d->ComboBox_TypeModifier, modifier); - if (modifierIndex != -1) + vtkSlicerTerminologyType* typeObject = entry->GetTypeObject(); + if (typeObject->GetHasModifiers()) { - d->ComboBox_TypeModifier->blockSignals(true); - d->ComboBox_TypeModifier->setCurrentIndex(modifierIndex); - d->ComboBox_TypeModifier->blockSignals(false); + // Get color from modifier if any + typeObject = entry->GetTypeModifierObject(); } - return (modifierIndex != -1); + + unsigned char colorChar[3] = {0,0,0}; + typeObject->GetRecommendedDisplayRGBValue(colorChar); + color.setRgb(colorChar[0], colorChar[1], colorChar[2]); + return color; } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onTypeModifierSelectionChanged(int index) +QColor qSlicerTerminologyNavigatorWidget::recommendedColorFromCurrentTerminology() { - Q_UNUSED(index); Q_D(qSlicerTerminologyNavigatorWidget); - // Invalidate type modifier if there are no modifiers for the current type - if (d->ComboBox_TypeModifier->count() == 0) - { - d->resetCurrentTypeModifier(); - return; - } - - vtkSmartPointer modifier = vtkSmartPointer::New(); - if (index < 0) + QColor color; + if (d->CurrentRegionContextName.isEmpty() || !d->CurrentCategoryObject || !d->CurrentTypeObject) { - // If new index is invalid (happens on clearing the combobox), then set empty modifier - this->setCurrentTypeModifier(modifier); - return; + qWarning() << Q_FUNC_INFO << ": Invalid current terminology"; + return color; } - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); - if (!logic) + vtkSlicerTerminologyType* typeObject = d->CurrentTypeObject; + if (d->CurrentTypeObject->GetHasModifiers()) { - qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; - return; + // Get color from modifier if any + typeObject = d->CurrentTypeModifierObject; } - // Get current modifier object is not None modifier is selected - if (index != 0 || !d->ComboBox_TypeModifier->itemData(index).isNull()) - { - QMap userData = d->ComboBox_TypeModifier->itemData(index).toMap(); - vtkSlicerTerminologiesModuleLogic::CodeIdentifier modifierId( - userData[QString::number(CodingSchemeDesignatorRole)].toString().toUtf8().constData(), - userData[QString::number(CodeValueRole)].toString().toUtf8().constData(), - d->ComboBox_TypeModifier->itemText(index).toUtf8().constData() ); - if (!logic->GetTypeModifierInTerminologyType( - d->CurrentTerminologyName.toUtf8().constData(), - vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(d->CurrentCategoryObject), - vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(d->CurrentTypeObject), - modifierId, modifier) ) - { - qCritical() << Q_FUNC_INFO << ": Failed to find modifier '" << d->ComboBox_TypeModifier->itemText(index); - return; - } - } + unsigned char colorChar[3] = {0,0,0}; + typeObject->GetRecommendedDisplayRGBValue(colorChar); + color.setRgb(colorChar[0], colorChar[1], colorChar[2]); + return color; +} - // Set current type modifier - this->setCurrentTypeModifier(modifier); +//----------------------------------------------------------------------------- +bool qSlicerTerminologyNavigatorWidget::regionSectionVisible() const +{ + Q_D(const qSlicerTerminologyNavigatorWidget); - // Generate name based on selection if not custom - if (d->NameAutoGenerated) - { - d->setNameFromCurrentTerminology(); - } - // Set recommended color to color picker if not custom - if (d->ColorAutoGenerated) - { - d->setRecommendedColorFromCurrentTerminology(); - } + return d->RegionExpandButton->isChecked(); } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onCategorySearchTextChanged(QString search) +void qSlicerTerminologyNavigatorWidget::setRegionSectionVisible(bool visible) { - Q_UNUSED(search); + Q_D(qSlicerTerminologyNavigatorWidget); - this->populateCategoryTable(); + d->RegionExpandButton->setChecked(visible); } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onTypeSearchTextChanged(QString search) +bool qSlicerTerminologyNavigatorWidget::overrideSectionVisible() const { - Q_UNUSED(search); - - this->populateTypeTable(); + Q_D(const qSlicerTerminologyNavigatorWidget); + return d->frame_TerminologyOverride->isVisible(); } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onNameChanged(QString name) +void qSlicerTerminologyNavigatorWidget::setOverrideSectionVisible(bool visible) { - Q_UNUSED(name); Q_D(qSlicerTerminologyNavigatorWidget); - - // Set as manual - d->NameAutoGenerated = false; - - // Enable reset name button - d->pushButton_ResetName->setEnabled(true); + d->frame_TerminologyOverride->setVisible(visible); } + //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onResetNameClicked() +void qSlicerTerminologyNavigatorWidget::onTerminologySelectionChanged(int index) { Q_D(qSlicerTerminologyNavigatorWidget); - d->setNameFromCurrentTerminology(); + + // Make selection in the other combobox as well + ctkComboBox* visibleComboBox = (d->ComboBox_Terminology->isVisible() ? + d->ComboBox_Terminology : d->ComboBox_Terminology_2); + ctkComboBox* invisibleComboBox = (!d->ComboBox_Terminology->isVisible() ? + d->ComboBox_Terminology : d->ComboBox_Terminology_2); + QSignalBlocker blocker(invisibleComboBox); + invisibleComboBox->setCurrentIndex(visibleComboBox->currentIndex()); + + // Set current terminology + QString terminologyName = visibleComboBox->itemText(index); + QString colorNodeId = visibleComboBox->itemData(index).toString(); + d->setCurrentTerminology(terminologyName, colorNodeId); } + //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onColorChanged(QColor color) +void qSlicerTerminologyNavigatorWidget::onCategorySelectionChanged() { - Q_UNUSED(color); Q_D(qSlicerTerminologyNavigatorWidget); - // Set as manual - d->ColorAutoGenerated = false; + QList selectedItems = d->tableWidget_Category->selectedItems(); + + if (selectedItems.count() == 0) + { + // Happens when clearing the table + return; + } + + // Reset current category because it will be set when the type is selected + d->resetCurrentCategory(); + // Reset current type and type modifier + d->resetCurrentType(); + d->resetCurrentTypeModifier(); + // Reset region information as well + d->resetCurrentRegion(); + d->resetCurrentRegionModifier(); + + // Get current category object + vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); + if (!logic) + { + qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; + return; + } + + bool showAnatomyOnInAnyCategories = false; + + d->SelectedCategoryObjects.clear(); + foreach (QTableWidgetItem* currentItem, selectedItems) + { + vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId( + currentItem->data(CodingSchemeDesignatorRole).toString().toUtf8().constData(), + currentItem->data(CodeValueRole).toString().toUtf8().constData(), + currentItem->text().toUtf8().constData() ); + vtkSmartPointer category = vtkSmartPointer::New(); + if (!logic->GetCategoryInTerminology( + d->CurrentTerminologyName.toUtf8().constData(), categoryId, category) ) + { + qCritical() << Q_FUNC_INFO << ": Failed to find category '" << currentItem->text(); + continue; + } + + showAnatomyOnInAnyCategories |= category->GetShowAnatomy(); + + d->SelectedCategoryObjects << category; + } + + // Populate type table, and reset type modifier combobox and region widgets + d->populateTypeTable(); + d->populateTypeModifierComboBox(); + d->populateRegionModifierComboBox(); + + // Only enable type table if there are items in it + if (d->tableWidget_Type->rowCount() == 1) // None item always present + { + d->ComboBox_TypeModifier->setEnabled(false); + } + else + { + d->SearchBox_Type->setEnabled(true); + } + + // Enable region controls if related flag is on + d->ComboBox_RegionContext->setEnabled(showAnatomyOnInAnyCategories); + d->tableWidget_Region->setEnabled(showAnatomyOnInAnyCategories); + d->SearchBox_Region->setEnabled(showAnatomyOnInAnyCategories); + d->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection + + // Selection is invalid until type is selected + emit selectionValidityChanged(false); - // Enable reset color button - d->pushButton_ResetColor->setEnabled(true); + // Generate name based on selection (empty name in this case) if not custom + if (d->NameAutoGenerated) + { + d->setNameFromCurrentTerminology(); + } } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onResetColorClicked() +void qSlicerTerminologyNavigatorWidget::onSelectAllCategoriesButtonClicked() { Q_D(qSlicerTerminologyNavigatorWidget); - d->setRecommendedColorFromCurrentTerminology(); + + d->tableWidget_Category->selectAll(); } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onLoadTerminologyClicked() +void qSlicerTerminologyNavigatorWidget::onTypeSelected(QTableWidgetItem* currentItem, QTableWidgetItem* previousItem) { + Q_UNUSED(previousItem); Q_D(qSlicerTerminologyNavigatorWidget); - const QString& terminologyFileName = - QFileDialog::getOpenFileName( this, "Select terminology json file...", QString(), - "Json files (*.json);; All files (*)" ); - if (!terminologyFileName.isEmpty()) - { - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); - if (!logic) - { - qCritical() << Q_FUNC_INFO << ": Invalid terminology logic"; - return; - } - QString loadedContextName = logic->LoadTerminologyFromFile(terminologyFileName.toUtf8().constData()).c_str(); - if (!loadedContextName.isEmpty()) - { - QMessageBox::information(this, "Load succeeded", - QString("Loading terminology context named %1 succeeded").arg(loadedContextName) ); - this->copyContextToUserDirectory(terminologyFileName); - } - else - { - QString errorMessage = - "Loading terminology from file %1 failed!

" - "Please check validity of the file using the online validator tool."; - QMessageBox::critical(this, "Load failed", errorMessage.arg(terminologyFileName)); - } + if (!currentItem) + { + d->resetCurrentType(); + d->resetCurrentTypeModifier(); + return; } -} -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onLoadRegionContextClicked() -{ - Q_D(qSlicerTerminologyNavigatorWidget); - const QString& regionContextFileName = - QFileDialog::getOpenFileName( this, "Select region context json file...", QString(), - "Json files (*.json);; All files (*)" ); - if (!regionContextFileName.isEmpty()) + // Reset terminology elements if none item is selected + if (!currentItem->text().compare(d->NoneItemName)) { - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); - if (!logic) - { - qCritical() << Q_FUNC_INFO << ": Invalid terminology logic"; - return; - } - QString loadedContextName = logic->LoadRegionContextFromFile(regionContextFileName.toUtf8().constData()).c_str(); - if (!loadedContextName.isEmpty()) - { - QMessageBox::information(this, "Load succeeded", - QString("Loading region context named %1 succeeded").arg(loadedContextName) ); - - this->copyContextToUserDirectory(regionContextFileName); - } - else - { - QString errorMessage = - "Loading region context from file %1 failed!

" - "Please check validity of the file using the online validator tool."; - QMessageBox::critical(this, "Load failed", errorMessage.arg(regionContextFileName)); - } + // Do not reset category because it invalidates entry when setting terminology + // programmatically using setTerminologyEntry + d->resetCurrentType(); + d->resetCurrentTypeModifier(); + d->resetCurrentRegion(); + d->resetCurrentRegionModifier(); + return; } -} -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::copyContextToUserDirectory(QString filePath) -{ - Q_D(qSlicerTerminologyNavigatorWidget); vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); if (!logic) { - qCritical() << Q_FUNC_INFO << ": Invalid terminology logic"; + qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; return; } - // Make sure the file can be copied to the settings folder - QDir settingsFolder(logic->GetUserContextsPath()); - if (!settingsFolder.exists()) + // Get category object for selected type object + vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId( + currentItem->data(CategoryCodingSchemeDesignatorRole).toString().toUtf8().constData(), + currentItem->data(CategoryCodeValueRole).toString().toUtf8().constData(), + currentItem->data(CategoryCodeMeaningRole).toString().toUtf8().constData() ); + vtkSmartPointer category = vtkSmartPointer::New(); + if (!logic->GetCategoryInTerminology( + d->CurrentTerminologyName.toUtf8().constData(), + categoryId, category) ) { - settingsFolder.mkpath(logic->GetUserContextsPath()); + qCritical() << Q_FUNC_INFO << ": Failed to find category '" << categoryId.CodeMeaning.c_str(); + return; } - if (!settingsFolder.exists()) + + // Get current type object + vtkSlicerTerminologiesModuleLogic::CodeIdentifier typeId( + currentItem->data(CodingSchemeDesignatorRole).toString().toUtf8().constData(), + currentItem->data(CodeValueRole).toString().toUtf8().constData(), + currentItem->text().toUtf8().constData() ); + vtkSmartPointer type = vtkSmartPointer::New(); + if (!logic->GetTypeInTerminologyCategory( + d->CurrentTerminologyName.toUtf8().constData(), categoryId, typeId, type) ) { - qCritical() << Q_FUNC_INFO << ": Settings folder '" << settingsFolder.absolutePath() << "' does not exist. Copying context file failed."; + qCritical() << Q_FUNC_INFO << ": Failed to find type '" << currentItem->text(); return; } - QString fileNameOnly = QFileInfo(filePath).fileName(); - QString targetFilePath = settingsFolder.absoluteFilePath(fileNameOnly); - // Check if there is a file with the same name in the settings folder and ask the user in that case - if (QFile::exists(targetFilePath)) + // Set current category object for selected type + d->CurrentCategoryObject->Copy(category); + d->updateWidgetFromCurrentCategory(); + // Set type from item + d->setCurrentType(type); + + // Update state of anatomy controls based on the category of the selected type + d->ComboBox_RegionContext->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); + d->tableWidget_Region->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); + d->SearchBox_Region->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); + d->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection + + // Generate name based on selection if not custom + if (d->NameAutoGenerated) { - QString message = QString(tr("There is a file with name '%1' in the stored contexts.\n\n" - "Do you wish to update the stored context file with the just loaded one?")).arg(fileNameOnly); - QMessageBox::StandardButton answer = - QMessageBox::question(nullptr, tr("Context file exists"), message, - QMessageBox::Yes | QMessageBox::No, QMessageBox::No ); - if (answer == QMessageBox::No) - { - return; - } - else - { - // Remove file before copying (copy function does not overwrite) - settingsFolder.remove(fileNameOnly); - } + d->setNameFromCurrentTerminology(); + } + // Set recommended color to color picker if not custom + if (d->ColorAutoGenerated) + { + d->setRecommendedColorFromCurrentTerminology(); } +} - // Copy file to settings folder for automatic loading on startup - QFile file(filePath); - file.copy(targetFilePath); +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidget::onTypeCellDoubleClicked(int row, int column) +{ + Q_UNUSED(row); + Q_UNUSED(column); + emit typeDoubleClicked(); } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::populateRegionContextComboBox() +void qSlicerTerminologyNavigatorWidget::onColorSelected(const QItemSelection& selected, const QItemSelection& deselected) { + Q_UNUSED(selected); + Q_UNUSED(deselected); Q_D(qSlicerTerminologyNavigatorWidget); - - d->ComboBox_RegionContext->clear(); - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); if (!logic) { + qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; return; } - - d->RegionContextComboboxPopulating = true; - std::vector regionContextNames; - logic->GetLoadedRegionContextNames(regionContextNames); - for (std::vector::iterator anIt=regionContextNames.begin(); anIt!=regionContextNames.end(); ++anIt) + if (!d->ColorTableView->currentIndex().isValid()) { - d->ComboBox_RegionContext->addItem(anIt->c_str()); + return; } - d->RegionContextComboboxPopulating = false; -} -//----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::populateRegionTable() -{ - Q_D(qSlicerTerminologyNavigatorWidget); - - d->tableWidget_Region->clearContents(); + // Get color node + vtkMRMLColorNode* colorNode = d->ColorTableView->mrmlColorNode(); - if (d->CurrentRegionContextName.isEmpty()) - { - d->tableWidget_Region->setRowCount(0); - return; - } + // Get color index from selected model index + int colorIndex = d->ColorTableView->selectedColorIndex(); - vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); - if (!logic) + if ( colorNode->GetTerminologyCategory(colorIndex) == nullptr || colorNode->GetTerminologyCategory(colorIndex)->GetCodeMeaning() == nullptr + || colorNode->GetTerminologyType(colorIndex) == nullptr || colorNode->GetTerminologyType(colorIndex)->GetCodeMeaning() == nullptr) { - qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic"; + qCritical() << Q_FUNC_INFO << ": Invalid terminology in selected color (" << colorNode->GetName() << ": " << colorIndex << ")"; + emit selectionValidityChanged(false); return; } - // Get region names containing the search string. If no search string then add every region - std::vector regions; - logic->FindRegionsInRegionContext( - d->CurrentRegionContextName.toUtf8().constData(), - regions, d->SearchBox_Region->text().toUtf8().constData() ); - - d->tableWidget_Region->setRowCount(regions.size() + 1); // +1 for the "None" item - - int index = 0; + // Reset current terminology information + d->resetCurrentCategory(); + d->resetCurrentType(); + d->resetCurrentTypeModifier(); + d->resetCurrentRegion(); + d->resetCurrentRegionModifier(); - // Add "None" item - QTableWidgetItem* noneRegionItem = new QTableWidgetItem(d->NoneItemName); - d->tableWidget_Region->setItem(index++, 0, noneRegionItem); - QTableWidgetItem* selectedItem = noneRegionItem; + // Set category and type + d->CurrentCategoryObject->vtkCodedEntry::Copy(colorNode->GetTerminologyCategory(colorIndex)); + d->CurrentTypeObject->vtkCodedEntry::Copy(colorNode->GetTerminologyType(colorIndex)); + double color[4] = {0.0}; + colorNode->GetColor(colorIndex, color); + d->CurrentTypeObject->SetRecommendedDisplayRGBValue( + (unsigned char)(color[0] * 255.0), (unsigned char)(color[1] * 255.0), (unsigned char)(color[2] * 255.0)); + emit selectionValidityChanged(true); - std::vector::iterator idIt; - for (idIt=regions.begin(); idIt!=regions.end(); ++idIt, ++index) + // Set optional information if any + if ( colorNode->GetTerminologyTypeModifier(colorIndex) != nullptr + && colorNode->GetTerminologyTypeModifier(colorIndex)->GetCodeMeaning() != nullptr ) { - vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedRegionId = (*idIt); - QString addedRegionName(addedRegionId.CodeMeaning.c_str()); - QTableWidgetItem* addedRegionItem = new QTableWidgetItem(addedRegionName); - addedRegionItem->setData(CodingSchemeDesignatorRole, QString(addedRegionId.CodingSchemeDesignator.c_str())); - addedRegionItem->setData(CodeValueRole, QString(addedRegionId.CodeValue.c_str())); - d->tableWidget_Region->setItem(index, 0, addedRegionItem); - - if ( d->CurrentRegionObject->GetCodingSchemeDesignator() && !addedRegionId.CodingSchemeDesignator.compare(d->CurrentRegionObject->GetCodingSchemeDesignator()) - && d->CurrentRegionObject->GetCodeValue() && !addedRegionId.CodeValue.compare(d->CurrentRegionObject->GetCodeValue()) ) - { - selectedItem = addedRegionItem; - } + d->CurrentTypeModifierObject->vtkCodedEntry::Copy(colorNode->GetTerminologyTypeModifier(colorIndex)); } - - // Select region if selection was valid and item shows up in search - if (selectedItem) + else + { + d->resetCurrentTypeModifier(); + } + if ( colorNode->GetTerminologyRegion(colorIndex) != nullptr + && colorNode->GetTerminologyRegion(colorIndex)->GetCodeMeaning() != nullptr ) + { + d->CurrentRegionObject->vtkCodedEntry::Copy(colorNode->GetTerminologyRegion(colorIndex)); + } + else { - d->tableWidget_Region->setCurrentItem(selectedItem); + d->resetCurrentRegion(); + } + if ( colorNode->GetTerminologyRegionModifier(colorIndex) != nullptr + && colorNode->GetTerminologyRegionModifier(colorIndex)->GetCodeMeaning() != nullptr ) + { + d->CurrentRegionModifierObject->vtkCodedEntry::Copy(colorNode->GetTerminologyRegionModifier(colorIndex)); + } + else + { + d->resetCurrentRegionModifier(); } + + // Set name and color + d->lineEdit_Name->setText(colorNode->GetColorName(colorIndex)); + d->NameAutoGenerated = false; + double rgba[4] = { 0.0, 0.0, 0.0, 0.0 }; + colorNode->GetColor(colorIndex, rgba); + d->ColorPickerButton_RecommendedRGB->setColor(QColor::fromRgbF(rgba[0], rgba[1], rgba[2], rgba[3])); + d->ColorAutoGenerated = false; +} +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidget::onColorRowDoubleClicked(const QModelIndex &index) +{ + Q_UNUSED(index); + emit colorDoubleClicked(); } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::populateRegionModifierComboBox() +void qSlicerTerminologyNavigatorWidget::onTypeModifierSelectionChanged(int index) { + Q_UNUSED(index); Q_D(qSlicerTerminologyNavigatorWidget); - d->ComboBox_RegionModifier->clear(); - - if (d->CurrentRegionContextName.isEmpty() || !d->CurrentRegionObject || !d->CurrentRegionObject->GetCodeValue()) + // Invalidate type modifier if there are no modifiers for the current type + if (d->ComboBox_TypeModifier->count() == 0) { - d->ComboBox_RegionModifier->setEnabled(false); + d->resetCurrentTypeModifier(); return; } - // If current region has no modifiers then leave it empty and disable - if (!d->CurrentRegionObject->GetHasModifiers()) + + vtkSmartPointer modifier = vtkSmartPointer::New(); + if (index < 0) { - d->ComboBox_RegionModifier->setEnabled(false); + // If new index is invalid (happens on clearing the combobox), then set empty modifier + d->setCurrentTypeModifier(modifier); return; } @@ -2431,162 +2469,177 @@ void qSlicerTerminologyNavigatorWidget::populateRegionModifierComboBox() return; } - // Get region modifier names - std::vector regionModifiers; - logic->GetRegionModifiersInRegion( - d->CurrentRegionContextName.toUtf8().constData(), - vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(d->CurrentRegionObject), - regionModifiers ); - - // Add "none" item - d->ComboBox_RegionModifier->addItem(tr("No region modifier")); - int selectedIndex = 0; - int index = 1; // "none" item is 0, start adding items from 1 - - std::vector::iterator idIt; - for (idIt=regionModifiers.begin(); idIt!=regionModifiers.end(); ++idIt, ++index) + // Get current modifier object is not None modifier is selected + if (index != 0 || !d->ComboBox_TypeModifier->itemData(index).isNull()) { - vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedRegionModifierId = (*idIt); - QString addedRegionModifierName(addedRegionModifierId.CodeMeaning.c_str()); - - QMap userData; - userData[QString::number(CodingSchemeDesignatorRole)] = QString(addedRegionModifierId.CodingSchemeDesignator.c_str()); - userData[QString::number(CodeValueRole)] = QString(addedRegionModifierId.CodeValue.c_str()); - d->ComboBox_RegionModifier->addItem(addedRegionModifierName, QVariant(userData)); - - if (!addedRegionModifierName.compare(d->CurrentRegionModifierObject->GetCodeMeaning())) + QMap userData = d->ComboBox_TypeModifier->itemData(index).toMap(); + vtkSlicerTerminologiesModuleLogic::CodeIdentifier modifierId( + userData[QString::number(CodingSchemeDesignatorRole)].toString().toUtf8().constData(), + userData[QString::number(CodeValueRole)].toString().toUtf8().constData(), + d->ComboBox_TypeModifier->itemText(index).toUtf8().constData() ); + if (!logic->GetTypeModifierInTerminologyType( + d->CurrentTerminologyName.toUtf8().constData(), + vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(d->CurrentCategoryObject), + vtkSlicerTerminologiesModuleLogic::GetCodeIdentifierFromCodedEntry(d->CurrentTypeObject), + modifierId, modifier) ) { - selectedIndex = index; + qCritical() << Q_FUNC_INFO << ": Failed to find modifier '" << d->ComboBox_TypeModifier->itemText(index); + return; } } - // Select modifier if selection was valid - if (selectedIndex != -1) + // Set current type modifier + d->setCurrentTypeModifier(modifier); + + // Generate name based on selection if not custom + if (d->NameAutoGenerated) + { + d->setNameFromCurrentTerminology(); + } + // Set recommended color to color picker if not custom + if (d->ColorAutoGenerated) { - d->ComboBox_RegionModifier->setCurrentIndex(selectedIndex); + d->setRecommendedColorFromCurrentTerminology(); } } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::setCurrentRegionContext(QString contextName) +void qSlicerTerminologyNavigatorWidget::onCategorySearchTextChanged(QString search) { + Q_UNUSED(search); Q_D(qSlicerTerminologyNavigatorWidget); + d->populateCategoryTable(); +} - // Reset current region and region modifier - d->resetCurrentRegion(); - d->resetCurrentRegionModifier(); +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidget::onTypeSearchTextChanged(QString search) +{ + Q_UNUSED(search); + Q_D(qSlicerTerminologyNavigatorWidget); + d->populateTypeTable(); +} - // Set current region context - d->CurrentRegionContextName = contextName; - if (contextName.isEmpty()) - { - return; - } +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidget::onNameChanged(QString name) +{ + Q_UNUSED(name); + Q_D(qSlicerTerminologyNavigatorWidget); - // Populate region table and reset region modifier combobox - this->populateRegionTable(); - this->populateRegionModifierComboBox(); + // Set as manual + d->NameAutoGenerated = false; - // Only enable region table if there are items in it - if (d->tableWidget_Region->rowCount() == 0) - { - d->tableWidget_Region->setEnabled(false); - if (d->SearchBox_Region->text().isEmpty()) - { - // Table might be empty because of a search - d->SearchBox_Region->setEnabled(false); - } - d->ComboBox_RegionModifier->setEnabled(false); - } - else if (d->CurrentCategoryObject->GetShowAnatomy()) - { - d->tableWidget_Region->setEnabled(true); - d->SearchBox_Region->setEnabled(true); - } + // Enable reset name button + d->pushButton_ResetName->setEnabled(true); } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::onRegionContextSelectionChanged(int index) +void qSlicerTerminologyNavigatorWidget::onResetNameClicked() +{ + Q_D(qSlicerTerminologyNavigatorWidget); + d->setNameFromCurrentTerminology(); +} + +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidget::onColorChanged(QColor color) { + Q_UNUSED(color); Q_D(qSlicerTerminologyNavigatorWidget); - // Set current region context - QString regionContextName = d->ComboBox_RegionContext->itemText(index); - this->setCurrentRegionContext(regionContextName); + // Set as manual + d->ColorAutoGenerated = false; - // Save last selection to application settings - if (!d->RegionContextComboboxPopulating) - { - QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings(); - settings->setValue("Terminology/LastRegionContext", regionContextName); - } + // Enable reset color button + d->pushButton_ResetColor->setEnabled(true); } //----------------------------------------------------------------------------- -bool qSlicerTerminologyNavigatorWidget::setCurrentRegion(vtkSlicerTerminologyType* region) +void qSlicerTerminologyNavigatorWidget::onResetColorClicked() { Q_D(qSlicerTerminologyNavigatorWidget); + d->setRecommendedColorFromCurrentTerminology(); +} - // Reset current region modifier - d->resetCurrentRegionModifier(); - - if (!region) +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidget::onLoadTerminologyClicked() +{ + Q_D(qSlicerTerminologyNavigatorWidget); + const QString& terminologyFileName = + QFileDialog::getOpenFileName( this, "Select terminology json file...", QString(), + "Json files (*.json);; All files (*)" ); + if (!terminologyFileName.isEmpty()) { - d->resetCurrentRegion(); - qCritical() << Q_FUNC_INFO << ": Invalid region object set"; - return false; - } + vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); + if (!logic) + { + qCritical() << Q_FUNC_INFO << ": Invalid terminology logic"; + return; + } + QString loadedContextName = logic->LoadTerminologyFromFile(terminologyFileName.toUtf8().constData()).c_str(); + if (!loadedContextName.isEmpty()) + { + QMessageBox::information(this, "Load succeeded", + QString("Loading terminology context named %1 succeeded").arg(loadedContextName) ); - // Ignore selection if current category does not support anatomy (possible due to multi-selection) - if (!d->CurrentCategoryObject) - { - d->resetCurrentRegion(); - qCritical() << Q_FUNC_INFO << ": Missing current category"; - return false; + d->copyContextToUserDirectory(terminologyFileName); + } + else + { + QString errorMessage = + "Loading terminology from file %1 failed!

" + "Please check validity of the file using the online validator tool."; + QMessageBox::critical(this, "Load failed", errorMessage.arg(terminologyFileName)); + } } +} - // Update state of anatomy controls based on the category of the selected type - // (the controls may have been enabled because some of the selected categories supported anatomy) - d->ComboBox_RegionContext->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); - d->tableWidget_Region->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); - d->SearchBox_Region->setEnabled(d->CurrentCategoryObject->GetShowAnatomy()); - d->ComboBox_RegionModifier->setEnabled(false); // Disabled until valid region selection - - // Reject region selection if current type's category does not support anatomy - if (!d->CurrentCategoryObject->GetShowAnatomy()) +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidget::onLoadRegionContextClicked() +{ + Q_D(qSlicerTerminologyNavigatorWidget); + const QString& regionContextFileName = + QFileDialog::getOpenFileName( this, "Select region context json file...", QString(), + "Json files (*.json);; All files (*)" ); + if (!regionContextFileName.isEmpty()) { - d->resetCurrentRegion(); - return false; - } - - // Set current region - d->CurrentRegionObject->Copy(region); + vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic(); + if (!logic) + { + qCritical() << Q_FUNC_INFO << ": Invalid terminology logic"; + return; + } + QString loadedContextName = logic->LoadRegionContextFromFile(regionContextFileName.toUtf8().constData()).c_str(); + if (!loadedContextName.isEmpty()) + { + QMessageBox::information(this, "Load succeeded", + QString("Loading region context named %1 succeeded").arg(loadedContextName) ); - // Populate region modifier combobox - this->populateRegionModifierComboBox(); + d->copyContextToUserDirectory(regionContextFileName); + } + else + { + QString errorMessage = + "Loading region context from file %1 failed!

" + "Please check validity of the file using the online validator tool."; + QMessageBox::critical(this, "Load failed", errorMessage.arg(regionContextFileName)); + } + } +} - // Only enable region modifier combobox if there are items in it - d->ComboBox_RegionModifier->setEnabled(d->ComboBox_RegionModifier->count()); +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidget::onRegionContextSelectionChanged(int index) +{ + Q_D(qSlicerTerminologyNavigatorWidget); - // "None" is selected - if (!region->GetCodeValue()) - { - d->resetCurrentRegion(); - d->tableWidget_Region->blockSignals(true); - d->tableWidget_Region->setCurrentCell(0, 0); - d->tableWidget_Region->blockSignals(false); - return false; - } + // Set current region context + QString regionContextName = d->ComboBox_RegionContext->itemText(index); + d->setCurrentRegionContext(regionContextName); - // Select region if found - QTableWidgetItem* regionItem = d->findTableWidgetItemForType(d->tableWidget_Region, region); - if (regionItem) + // Save last selection to application settings + if (!d->RegionContextComboboxPopulating) { - d->tableWidget_Region->blockSignals(true); - d->tableWidget_Region->setCurrentItem(regionItem); - d->tableWidget_Region->blockSignals(false); + QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings(); + settings->setValue("Terminology/LastRegionContext", regionContextName); } - return regionItem; // Return true if region found and selected } //----------------------------------------------------------------------------- @@ -2626,7 +2679,7 @@ void qSlicerTerminologyNavigatorWidget::onRegionSelected(QTableWidgetItem* curre } } // Set current region - this->setCurrentRegion(region); + d->setCurrentRegion(region); // Generate name based on selection if not custom if (d->NameAutoGenerated) @@ -2635,32 +2688,6 @@ void qSlicerTerminologyNavigatorWidget::onRegionSelected(QTableWidgetItem* curre } } -//----------------------------------------------------------------------------- -bool qSlicerTerminologyNavigatorWidget::setCurrentRegionModifier(vtkSlicerTerminologyType* modifier) -{ - Q_D(qSlicerTerminologyNavigatorWidget); - - if (!modifier) - { - d->resetCurrentRegionModifier(); - qCritical() << Q_FUNC_INFO << ": Invalid region modifier object set"; - return false; - } - - // Set current type modifier - d->CurrentRegionModifierObject->Copy(modifier); - - // Select modifier if found - int modifierIndex = d->findComboBoxIndexForModifier(d->ComboBox_RegionModifier, modifier); - if (modifierIndex != -1) - { - d->ComboBox_RegionModifier->blockSignals(true); - d->ComboBox_RegionModifier->setCurrentIndex(modifierIndex); - d->ComboBox_RegionModifier->blockSignals(false); - } - return (modifierIndex != -1); -} - //----------------------------------------------------------------------------- void qSlicerTerminologyNavigatorWidget::onRegionModifierSelectionChanged(int index) { @@ -2671,7 +2698,7 @@ void qSlicerTerminologyNavigatorWidget::onRegionModifierSelectionChanged(int ind if (index < 0) { // If new index is invalid (happens on clearing the combobox), then set empty modifier - this->setCurrentRegionModifier(modifier); + d->setCurrentRegionModifier(modifier); return; } @@ -2701,7 +2728,7 @@ void qSlicerTerminologyNavigatorWidget::onRegionModifierSelectionChanged(int ind } // Set current region modifier - this->setCurrentRegionModifier(modifier); + d->setCurrentRegionModifier(modifier); // Generate name based on selection if not custom if (d->NameAutoGenerated) @@ -2714,8 +2741,8 @@ void qSlicerTerminologyNavigatorWidget::onRegionModifierSelectionChanged(int ind void qSlicerTerminologyNavigatorWidget::onRegionSearchTextChanged(QString search) { Q_UNUSED(search); - - this->populateRegionTable(); + Q_D(qSlicerTerminologyNavigatorWidget); + d->populateRegionTable(); } //----------------------------------------------------------------------------- @@ -2723,23 +2750,35 @@ void qSlicerTerminologyNavigatorWidget::onLogicModified() { Q_D(qSlicerTerminologyNavigatorWidget); - this->populateTerminologyComboBox(); + d->populateTerminologyComboBox(); d->resetCurrentCategory(); - this->populateRegionContextComboBox(); + d->populateRegionContextComboBox(); d->resetCurrentRegion(); } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::regionExpandButtonUp() +void qSlicerTerminologyNavigatorWidget::onRegionExpandButtonUp() { Q_D(qSlicerTerminologyNavigatorWidget); d->RegionExpandButton->setDown(false); } //----------------------------------------------------------------------------- -void qSlicerTerminologyNavigatorWidget::regionExpandButtonDown() +void qSlicerTerminologyNavigatorWidget::onRegionExpandButtonDown() { Q_D(qSlicerTerminologyNavigatorWidget); d->RegionExpandButton->setDown(true); } + +//----------------------------------------------------------------------------- +void qSlicerTerminologyNavigatorWidget::scrollToSelectedColorAfterLayout() +{ + Q_D(qSlicerTerminologyNavigatorWidget); + int colorIndex = d->ColorTableView->selectedColorIndex(); + if (colorIndex >= 0) + { + d->ColorTableView->selectColorByIndex(colorIndex); + } + d->ColorTableView->setFocus(); +} diff --git a/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyNavigatorWidget.h b/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyNavigatorWidget.h index f9b6b18848d..7410bbb276c 100644 --- a/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyNavigatorWidget.h +++ b/Modules/Loadable/Terminologies/Widgets/qSlicerTerminologyNavigatorWidget.h @@ -125,50 +125,6 @@ public slots: /// Show/hide name and color override section void setOverrideSectionVisible(bool); -protected: - /// Set current terminology to widget - void setCurrentTerminology(QString terminologyName); - /// Set current category to widget. - /// Only used when setting the category from a given entry to the widget! - /// \return Flag indicating whether the given category was found in the category table - bool setCurrentCategory(vtkSlicerTerminologyCategory* category); - /// Update widget UI based on the current category selection - void updateWidgetFromCurrentCategory(); - /// Set current type to widget - /// \return Flag indicating whether the given type was found in the type table - bool setCurrentType(vtkSlicerTerminologyType* type); - /// Set current type modifier to widget - /// \return Flag indicating whether the given modifier was found in the combobox - bool setCurrentTypeModifier(vtkSlicerTerminologyType* modifier); - /// Set current region context to widget - void setCurrentRegionContext(QString contextName); - /// Set current region to widget - /// \return Flag indicating whether the given region was found in the region table - bool setCurrentRegion(vtkSlicerTerminologyType* region); - /// Set current region modifier to widget - /// \return Flag indicating whether the given modifier was found in the combobox - bool setCurrentRegionModifier(vtkSlicerTerminologyType* modifier); - -protected: - /// Populate terminology combobox based on current selection - void populateTerminologyComboBox(); - /// Populate category table based on selected terminology and category search term - void populateCategoryTable(); - /// Populate type table based on selected category and type search term - void populateTypeTable(); - /// Populate type modifier combobox based on current selection - void populateTypeModifierComboBox(); - - /// Populate region context combobox based on current selection - void populateRegionContextComboBox(); - /// Populate region table based on selected region context and type search term - void populateRegionTable(); - /// Populate region modifier combobox based on current selection - void populateRegionModifierComboBox(); - - /// Copy terminology or region context file to user folder - void copyContextToUserDirectory(QString filePath); - protected slots: void onTerminologySelectionChanged(int); void onCategorySelectionChanged(); @@ -195,11 +151,13 @@ protected slots: void onLoadTerminologyClicked(); void onLoadRegionContextClicked(); - void regionExpandButtonUp(); - void regionExpandButtonDown(); + void onRegionExpandButtonUp(); + void onRegionExpandButtonDown(); void onLogicModified(); + void scrollToSelectedColorAfterLayout(); + signals: /// Emitted when selection becomes valid (true argument) or invalid (false argument) void selectionValidityChanged(bool);