Skip to content
/ Slicer Public
forked from Slicer/Slicer

Commit

Permalink
BUG: Make table columns translatable
Browse files Browse the repository at this point in the history
Table column name (non-translatable) and title (displayed, translatable) can now be set separately.
This allows displaying tables in any language while allowing modules to retrieve columns of tables using language-independent string identifiers.

Updated Segment Statistics module to make all plugins and measurements translatable.

To reduce complexity of the Tables module API, column "title" has replaced "long name". Long name was barely used (if at all) and its role would have been unclear (since we have name, title, description columns already).
Improved appearance of table column tooltips (reordered, improved formatting, added display of all custom properties)., use acronym of plugin instead of (1), (2), ... for distinguishing columns that would have had the same name.

see Slicer#7217
  • Loading branch information
Papa96108 authored and lassoan committed Dec 6, 2023
1 parent 47cae58 commit d369841
Show file tree
Hide file tree
Showing 19 changed files with 525 additions and 229 deletions.
49 changes: 40 additions & 9 deletions Libs/MRML/Core/vtkMRMLTableNode.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
static const char SCHEMA_COLUMN_NAME[] = "columnName";
static const char SCHEMA_COLUMN_TYPE[] = "type";
static const char SCHEMA_COLUMN_NULL_VALUE[] = "nullValue";
static const char SCHEMA_COLUMN_LONG_NAME[] = "longName";
static const char SCHEMA_COLUMN_TITLE[] = "title";
static const char SCHEMA_COLUMN_DESCRIPTION[] = "description";
static const char SCHEMA_COLUMN_UNIT_LABEL[] = "unitLabel";
static const char SCHEMA_COMPONENT_NAMES[] = "componentNames";
Expand All @@ -66,7 +66,7 @@ vtkMRMLTableNode::vtkMRMLTableNode()
this->Table = nullptr;
this->Schema = nullptr;
this->Locked = false;
this->UseColumnNameAsColumnHeader = false;
this->UseColumnTitleAsColumnHeader = false;
this->UseFirstColumnAsRowHeader = false;
this->HideFromEditorsOff();

Expand Down Expand Up @@ -96,7 +96,7 @@ void vtkMRMLTableNode::WriteXML(ostream& of, int nIndent)
Superclass::WriteXML(of, nIndent);
of << " locked=\"" << (this->GetLocked() ? "true" : "false") << "\"";
of << " useFirstColumnAsRowHeader=\"" << (this->GetUseFirstColumnAsRowHeader() ? "true" : "false") << "\"";
of << " useColumnNameAsColumnHeader=\"" << (this->GetUseColumnNameAsColumnHeader() ? "true" : "false") << "\"";
of << " useColumnTitleAsColumnHeader=\"" << (this->GetUseColumnTitleAsColumnHeader() ? "true" : "false") << "\"";
}


Expand All @@ -117,9 +117,10 @@ void vtkMRMLTableNode::ReadXMLAttributes(const char** atts)
{
this->SetLocked(strcmp(attValue,"true")?false:true);
}
else if (!strcmp(attName, "useColumnNameAsColumnHeader"))
else if (!strcmp(attName, "useColumnTitleAsColumnHeader")
|| !strcmp(attName, "useColumnNameAsColumnHeader")) // in legacy scenes
{
this->SetUseColumnNameAsColumnHeader(strcmp(attValue,"true")?false:true);
this->SetUseColumnTitleAsColumnHeader(strcmp(attValue,"true")?false:true);
}
else if (!strcmp(attName, "useFirstColumnAsRowHeader"))
{
Expand Down Expand Up @@ -176,7 +177,7 @@ void vtkMRMLTableNode::CopyContent(vtkMRMLNode* anode, bool deepCopy/*=true*/)
this->Table->Modified();
}
this->SetLocked(node->GetLocked());
this->SetUseColumnNameAsColumnHeader(node->GetUseColumnNameAsColumnHeader());
this->SetUseColumnTitleAsColumnHeader(node->GetUseColumnTitleAsColumnHeader());
this->SetUseFirstColumnAsRowHeader(node->GetUseFirstColumnAsRowHeader());
}

Expand Down Expand Up @@ -206,7 +207,7 @@ void vtkMRMLTableNode::PrintSelf(ostream& os, vtkIndent indent)
{
this->Superclass::PrintSelf(os,indent);
os << indent << "\nLocked: " << this->GetLocked();
os << indent << "\nUseColumnNameAsColumnHeader: " << this->GetUseColumnNameAsColumnHeader();
os << indent << "\nUseColumnTitleAsColumnHeader: " << this->GetUseColumnTitleAsColumnHeader();
os << indent << "\nUseFirstColumnAsRowHeader: " << this->GetUseFirstColumnAsRowHeader();
os << indent << "\nColumns:";
vtkTable* table = this->GetTable();
Expand Down Expand Up @@ -981,13 +982,27 @@ int vtkMRMLTableNode::GetColumnValueTypeFromSchema(const std::string& columnName
//----------------------------------------------------------------------------
void vtkMRMLTableNode::SetColumnLongName(const std::string& columnName, const std::string& longName)
{
this->SetColumnProperty(columnName, SCHEMA_COLUMN_LONG_NAME, longName);
vtkWarningMacro("vtkMRMLTableNode::SetColumnLongName is deprecated, use vtkMRMLSequenceBrowserNode::SetColumnTitle method instead");
this->SetColumnTitle(columnName, longName);
}

//----------------------------------------------------------------------------
std::string vtkMRMLTableNode::GetColumnLongName(const std::string& columnName)
{
return this->GetColumnProperty(columnName, SCHEMA_COLUMN_LONG_NAME);
vtkWarningMacro("vtkMRMLTableNode::GetColumnLongName is deprecated, use vtkMRMLSequenceBrowserNode::GetColumnTitle method instead");
return this->GetColumnTitle(columnName);
}

//----------------------------------------------------------------------------
void vtkMRMLTableNode::SetColumnTitle(const std::string& columnName, const std::string& title)
{
this->SetColumnProperty(columnName, SCHEMA_COLUMN_TITLE, title);
}

//----------------------------------------------------------------------------
std::string vtkMRMLTableNode::GetColumnTitle(const std::string& columnName)
{
return this->GetColumnProperty(columnName, SCHEMA_COLUMN_TITLE);
}

//----------------------------------------------------------------------------
Expand Down Expand Up @@ -1354,3 +1369,19 @@ std::vector<std::string> vtkMRMLTableNode::GetComponentNamesFromArray(vtkAbstrac
}
return componentNames;
}

//----------------------------------------------------------------------------
void vtkMRMLTableNode::SetUseColumnNameAsColumnHeader(bool useColumnTitle)
{
vtkWarningMacro("vtkMRMLTableNode::SetUseColumnNameAsColumnHeader is deprecated."
" Use vtkMRMLSequenceBrowserNode::SetUseColumnTitleAsColumnHeader method instead");
this->SetUseColumnTitleAsColumnHeader(useColumnTitle);
}

//----------------------------------------------------------------------------
bool vtkMRMLTableNode::GetUseColumnNameAsColumnHeader()
{
vtkWarningMacro("vtkMRMLTableNode::GetUseColumnNameAsColumnHeader is deprecated."
" Use vtkMRMLSequenceBrowserNode::GetUseColumnTitleAsColumnHeader method instead");
return this->GetUseColumnTitleAsColumnHeader();
}
79 changes: 53 additions & 26 deletions Libs/MRML/Core/vtkMRMLTableNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,40 +84,53 @@ class VTK_MRML_EXPORT vtkMRMLTableNode : public vtkMRMLStorableNode
virtual void SetAndObserveTable(vtkTable* table);
vtkGetObjectMacro(Table, vtkTable);

///
///@{
/// Set schema table
///
/// Each row of the schema table contains description of a data table column. Columns of the schema table:
/// - columnName: name of the data table column that properties are defined for (required)
/// This name is used by modules to look up columns in a table, therefore it must not be translated.
/// Column name \<default\> is reserved for defining default properties for new columns.
/// - type: data type of the column. Supported types: string, double, float, int, unsigned int, bit,
/// short, unsigned short, long, unsigned long, char, signed char, unsigned char, long long, unsigned long long, idtype.
/// Default: string.
/// - nullValue: value to be used when a value is not specified (new table row is added, blank string is entered, etc)
/// - longName: full human-readable name of the column
/// - description: human-readable detailed description of the column
/// - unitLabel: simple unit label
/// - title: human-readable name of the column (translatable)
/// The property name was formerly called "longName".
/// - description: detailed description of the column (translatable)
/// - unitLabel: unit label displayed along with the title (translatable)
/// - unitCodeMeaning: standard unit definition. Example: Standardized Uptake Value body weight.
/// Should not be translated, as in the future translation of standard terms will be implemented based on code value and scheme.
/// - unitCodeValue: standard unit definition. Example: {SUVbw}g/ml.
/// - unitCodingSchemeDesignator: standard unit definition. Example: UCUM.
virtual void SetAndObserveSchema(vtkTable* schema);
vtkGetObjectMacro(Schema, vtkTable);
///@}

///
///@{
/// Table contents cannot be edited through the user interface
vtkGetMacro(Locked, bool);
vtkSetMacro(Locked, bool);
///@}

///@{
/// First column should be treated as row label
vtkGetMacro(UseFirstColumnAsRowHeader, bool);
vtkSetMacro(UseFirstColumnAsRowHeader, bool);
///@}

///
/// Column name should be treated as column label
vtkGetMacro(UseColumnNameAsColumnHeader, bool);
vtkSetMacro(UseColumnNameAsColumnHeader, bool);
///@{
/// Column title should be displayed as column label
vtkGetMacro(UseColumnTitleAsColumnHeader, bool);
vtkSetMacro(UseColumnTitleAsColumnHeader, bool);
///@}

///@{
/// Deprecated. Use GetUseColumnTitleAsColumnHeader/SetUseColumnTitleAsColumnHeader instead.
bool GetUseColumnNameAsColumnHeader();
void SetUseColumnNameAsColumnHeader(bool useColumnTitle);
///@}

///
/// Create default storage node or nullptr if does not have one
vtkMRMLStorageNode* CreateDefaultStorageNode() override;

Expand Down Expand Up @@ -171,13 +184,13 @@ class VTK_MRML_EXPORT vtkMRMLTableNode : public vtkMRMLStorableNode
/// GetTable() method and manipulate that directly.
bool SetCellText(int rowIndex, int columnIndex, const char* text);

///
///@{
/// Get column index of the first column by the specified name.
/// Returns -1 if no such column is found.
int GetColumnIndex(const char* columnName);
int GetColumnIndex(const std::string &columnName);
///@}

///
/// Get column index from column pointer.
/// Returns -1 if column is not found.
int GetColumnIndex(vtkAbstractArray* column);
Expand All @@ -191,51 +204,65 @@ class VTK_MRML_EXPORT vtkMRMLTableNode : public vtkMRMLStorableNode
/// Convenience method for getting number of rows in the table.
int GetNumberOfRows();

///
/// Convenience method for getting number of columns in the table.
int GetNumberOfColumns();

///
///@{
/// Set null value for blank rows and missing values.
void SetColumnNullValue(const std::string& columnName, const std::string& nullValue);
std::string GetColumnNullValue(const std::string& columnName);
///@}

///
///@{
/// Set a full human-readable name of a column.
/// When there is no space constraints, the full name of the column may` displayed
/// instead/in addition to columnName to identify a column.
/// This used to be a field for storing a displayable title that was longer than the column name.
/// It is now deprecated. Use ColumnTitle instead for storing displayable name.
void SetColumnLongName(const std::string& columnName, const std::string& description);
std::string GetColumnLongName(const std::string& columnName);
///@}

///
///@{
/// Set a title of a column that is displayed on the user interface.
/// It is stored in the application's current language.
void SetColumnTitle(const std::string& columnName, const std::string& description);
std::string GetColumnTitle(const std::string& columnName);
///@}

///@{
/// Set human-readable description of a column.
/// It is stored in the application's current language.
void SetColumnDescription(const std::string& columnName, const std::string& description);
std::string GetColumnDescription(const std::string& columnName);
///@}

///
/// Set measurement unit for the data stored in the selected column.
///@{
/// Set displayed measurement unit for the data stored in the selected column.
/// It is stored in the application's current language.
/// For computations and unit conversions, it is recommended to store unit as coded entry in custom column properties.
void SetColumnUnitLabel(const std::string& columnName, const std::string& unitLabel);
std::string GetColumnUnitLabel(const std::string& columnName);
///@}

///
///@{
/// Get a column property.
/// Property name "columnName" is reserved for internal use.
/// \sa SetAndObserveSchema, GetColumnValueTypeFromSchema
std::string GetColumnProperty(const std::string& columnName, const std::string& propertyName);
std::string GetColumnProperty(int columnIndex, const std::string& propertyName);
///@}

///
/// Get list of all column property names.
/// \sa SetAndObserveSchema
void GetAllColumnPropertyNames(vtkStringArray* propertyNames);

///
///@{
/// Set a column property value.
/// Property name "columnName" is reserved for internal use.
/// Property name "type" converts existing values in the column.
/// \sa SetAndObserveSchema
void SetColumnProperty(const std::string& columnName, const std::string& propertyName, const std::string& propertyValue);
void SetColumnProperty(int columnIndex, const std::string& propertyName, const std::string& propertyValue);
///@}

///
/// Set a column property value.
Expand All @@ -246,12 +273,12 @@ class VTK_MRML_EXPORT vtkMRMLTableNode : public vtkMRMLStorableNode
///
/// Copy all properties from one column to another.
void CopyAllColumnProperties(const std::string& sourceColumnName, const std::string& targetColumnName);

///
///@{
/// Remove all properties defined for the specified column.
/// To remove all properties for all columns, use SetAndObserveScheme(nullptr).
void RemoveAllColumnProperties(const std::string& columnName);
void RemoveAllColumnProperties(int columnIndex);
///@}

/// Get column type stored in the schema as VTK type ID. It should only be used during reading/writing of the node,
/// because once the table column is created, the actual column type is the type of the associated VTK data array.
Expand Down Expand Up @@ -335,7 +362,7 @@ class VTK_MRML_EXPORT vtkMRMLTableNode : public vtkMRMLStorableNode

vtkTable* Table;
bool Locked;
bool UseColumnNameAsColumnHeader;
bool UseColumnTitleAsColumnHeader;
bool UseFirstColumnAsRowHeader;

vtkTable* Schema;
Expand Down
21 changes: 21 additions & 0 deletions Libs/MRML/Core/vtkMRMLTableStorageNode.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ vtkMRMLTableStorageNode::vtkMRMLTableStorageNode()
{
this->DefaultWriteFileExtension = "tsv";
this->AutoFindSchema = true;
this->ReadLongNameAsTitle = false;
}

//----------------------------------------------------------------------------
Expand Down Expand Up @@ -678,6 +679,26 @@ bool vtkMRMLTableStorageNode::ReadSchema(std::string filename, vtkMRMLTableNode*
return false;
}

if (this->ReadLongNameAsTitle)
{
// Update old tables that stored column title in "longName" property instead of "title"
vtkStringArray* longNameArray = vtkStringArray::SafeDownCast(schemaTable->GetColumnByName("longName"));
if (longNameArray)
{
vtkStringArray* titleArray = vtkStringArray::SafeDownCast(schemaTable->GetColumnByName("title"));
if (titleArray)
{
vtkWarningToMessageCollectionMacro(this->GetUserMessages(), "vtkMRMLTableStorageNode::ReadSchema",
"Schema file " << filename << " has both `longName` and 'title' properties defined, only content of 'title' property will be used.");
}
else
{
// Rename deprecated "longName" column property to "title"
longNameArray->SetName("title");
}
}
}

tableNode->SetAndObserveSchema(schemaTable);

return true;
Expand Down
8 changes: 8 additions & 0 deletions Libs/MRML/Core/vtkMRMLTableStorageNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ class VTK_MRML_EXPORT vtkMRMLTableStorageNode : public vtkMRMLStorageNode
vtkGetMacro(AutoFindSchema, bool);
vtkBooleanMacro(AutoFindSchema, bool);

/// Load legacy "longName" property as "title".
/// Enable for loading an old table file that used "longName" property to store column title.
/// Disabled by default, as "longName" is often too long to be used as title.
vtkSetMacro(ReadLongNameAsTitle, bool);
vtkGetMacro(ReadLongNameAsTitle, bool);
vtkBooleanMacro(ReadLongNameAsTitle, bool);

protected:
vtkMRMLTableStorageNode();
~vtkMRMLTableStorageNode() override;
Expand Down Expand Up @@ -121,6 +128,7 @@ class VTK_MRML_EXPORT vtkMRMLTableStorageNode : public vtkMRMLStorageNode
bool WriteSchema(std::string filename, vtkMRMLTableNode* tableNode);

bool AutoFindSchema;
bool ReadLongNameAsTitle;
};

#endif

0 comments on commit d369841

Please sign in to comment.