편집 가능한 트리 모델 예시
이 예제는 모델/뷰 프레임워크의 다른 클래스와 함께 사용할 수 있는 간단한 항목 기반 트리 모델을 구현하는 방법을 보여줍니다.

이 모델은 편집 가능한 항목, 사용자 정의 헤더, 행 및 열 삽입 및 제거 기능을 지원합니다. 이러한 기능을 통해 새로운 하위 항목을 삽입할 수도 있으며, 이에 대한 내용은 지원 예제 코드에서 확인할 수 있습니다.
개요
'모델 서브클래싱 참조'에 설명된 바와 같이, 모델은 표준 모델 함수 세트인 flags(), data(), headerData(), columnCount() 및 rowCount()에 대한 구현을 제공해야 합니다. 또한, 이 모델과 같은 계층적 모델은 index() 및 parent()에 대한 구현을 제공해야 합니다.
편집 가능한 모델은 setData() 및 setHeaderData()의 구현을 제공해야 하며, flags() 함수에서 적절한 플래그 조합을 반환해야 합니다.
이 예제에서는 모델의 차원을 변경할 수 있으므로, insertRows(), insertColumns(), removeRows() 및 removeColumns()도 구현해야 합니다.
설계
'Simple Tree Model' 예제와 마찬가지로, 이 모델은 TreeItem 클래스의 인스턴스 모음을 감싸는 래퍼 역할을 합니다. 각 TreeItem 는 트리 뷰의 한 행에 해당하는 항목 데이터를 보관하도록 설계되었으므로, 각 열에 표시되는 데이터에 해당하는 값들의 목록을 포함하고 있습니다.
QTreeView 는 모델에 대한 행 기반 뷰를 제공하므로, 모델을 통해 이러한 종류의 뷰에 데이터를 공급할 데이터 구조에 행 기반 설계를 선택하는 것이 자연스럽습니다. 이로 인해 트리 모델의 유연성이 떨어지고, 더 정교한 뷰와 함께 사용하기에는 다소 불편할 수 있지만, 설계 복잡성을 줄이고 구현을 더 쉽게 만들어 줍니다.
![]() | 내부 항목 간의 관계 사용자 정의 모델과 함께 사용할 데이터 구조를 설계할 때는 TreeItem::parent() 와 같은 함수를 통해 각 항목의 부모를 노출하는 것이 유용합니다. 이는 모델 자체의 parent() 함수를 작성하는 것을 더 쉽게 만들어 주기 때문입니다. 마찬가지로, TreeItem::child() 와 같은 함수는 모델의 index() 함수를 구현할 때 도움이 됩니다. 결과적으로 각 이 다이어그램은 표시된 예제에서, 루트 항목의 child() 함수를 호출하여 두 개의 최상위 항목인 A와 B를 얻을 수 있으며, 이 항목들은 각각 parent() 함수를 통해 루트 노드를 반환합니다. 단, 이 예시에서는 항목 A에 대해서만 표시되어 있습니다. |
각 TreeItem 는 자신이 나타내는 행의 각 열에 대한 데이터를 itemData 비공개 멤버( QVariant 객체 목록)에 저장합니다. 뷰의 각 열과 목록의 각 항목 간에는 일대일 매핑 관계가 있으므로, itemData 목록의 항목을 읽기 위한 간단한 data() 함수와 항목을 수정할 수 있도록 하는 setData() 함수를 제공합니다. 이 항목의 다른 함수들과 마찬가지로, 이를 통해 모델의 ` data()` 및 ` setData()` 함수 구현이 간소화됩니다.
항목 트리의 루트에 항목을 배치합니다. 이 루트 항목은 모델 인덱스 처리 시 최상위 항목의 부모를 나타내는 데 사용되는 null 모델 인덱스인 QModelIndex()에 해당합니다. 루트 항목은 표준 뷰에서 가시적으로 표시되지는 않지만, 내부 QVariant 객체 목록을 사용하여 수평 헤더 제목으로 사용될 문자열 목록을 저장합니다.
![]() | 모델을 통한 데이터 접근 다이어그램에 표시된 경우, a 로 표현되는 정보는 표준 모델/뷰 API를 사용하여 얻을 수 있습니다: 각 `item`은 주어진 행의 각 열에 대한 데이터를 포함하고 있으므로, 동일한 ` b와 같은 행에 있는 다른 모델 인덱스에 대한 정보를 얻기 위해서도 동일한 기본 |
모델 클래스인 ` TreeModel`에서는, `index()` 및 `parent() ` 메서드 구현 내에서 ` QAbstractItemModel::createIndex()`를 통해 해당 모델 인덱스를 생성할 때 각 항목에 대한 포인터를 전달함으로써 ` TreeItem ` 객체를 모델 인덱스와 연관시킵니다. 이러한 방식으로 저장된 포인터는 관련 모델 인덱스에서 internalPointer() 함수를 호출하여 가져올 수 있습니다. 이를 위해 자체 getItem() 함수를 생성하여 작업을 수행하게 하고, data() 및 parent() 구현에서 이 함수를 호출합니다.
internalPointer()에서 얻은 주소가 유효한 포인터라고 가정할 수 있으므로, 항목의 생성 및 소멸 방식을 제어할 수 있을 때 항목에 대한 포인터를 저장하는 것이 편리합니다. 그러나 일부 모델은 시스템 내의 다른 컴포넌트에서 얻은 항목을 처리해야 하며, 많은 경우 항목의 생성 또는 소멸 방식을 완전히 제어할 수 없습니다. 이러한 상황에서는 순수한 포인터 기반 접근 방식에 안전 장치를 추가하여, 모델이 이미 삭제된 항목에 접근하지 못하도록 보장해야 합니다.
| 기본 데이터 구조에 정보 저장 여러 데이터 항목이 각 이 다이어그램은 앞의 두 다이어그램에서 레이블 a, b, c로 표시된 정보 조각들이 기본 데이터 구조의 항목 A, B, C에 어떻게 저장되는지를 보여줍니다. 모델 내 동일한 행에 속한 정보 조각들은 모두 동일한 항목에서 가져온다는 점에 유의하십시오. 리스트의 각 요소는 모델 내 특정 행의 각 열이 노출하는 정보 조각 하나에 해당합니다. | ![]() |
TreeModel 구현은 QTreeView 와 함께 사용하도록 설계되었기 때문에, TreeItem 인스턴스를 사용하는 방식에 제한을 추가했습니다. 즉, 각 항목은 동일한 수의 데이터 열을 노출해야 합니다. 이를 통해 모델 보기에서 일관성을 유지할 수 있으며, 루트 항목을 사용하여 주어진 행의 열 수를 결정할 수 있게 됩니다. 또한 총 열 수에 해당하는 충분한 데이터를 포함하는 항목을 생성해야 한다는 요구 사항만 추가될 뿐입니다. 결과적으로, 모든 항목을 수정하기 위해 트리 전체를 탐색해야 하므로 열을 삽입하거나 제거하는 작업은 시간이 많이 소요되는 작업이 됩니다.
대안적인 접근 방식은 데이터 항목이 수정될 때마다 개별 ` TreeItem ` 인스턴스의 데이터 목록을 잘라내거나 확장하도록 ` TreeModel ` 클래스를 설계하는 것입니다. 그러나 이러한 "지연" 크기 조정 방식은 각 행의 끝 부분에서만 열을 삽입하거나 제거할 수 있게 할 뿐, 각 행의 임의 위치에서 열을 삽입하거나 제거하는 것은 허용하지 않습니다.
![]() | 모델 인덱스를 사용하여 항목 연결하기 'Simple Tree Model' 예제와 마찬가지로, ` 이 다이어그램에서는 이전 다이어그램에 표시된 항목들을 사용하여, 모델의 parent() 구현체가 호출자가 제공한 항목의 부모에 해당하는 모델 인덱스를 어떻게 얻는지 보여줍니다. QModelIndex::internalPointer() 함수를 사용하여 해당 모델 인덱스에서 항목 C에 대한 포인터를 얻습니다. 이 포인터는 인덱스가 생성될 때 내부적으로 저장되었습니다. 자식 모델은 부모에 대한 포인터를 포함하고 있으므로, parent() 함수를 사용하여 항목 B에 대한 포인터를 얻습니다. 부모 모델 인덱스는 QAbstractItemModel::createIndex() 함수를 사용하여 생성되며, 이때 항목 B에 대한 포인터를 내부 포인터로 전달합니다. |
TreeItem 클래스 정의
TreeItem 클래스는 부모 및 자식 항목에 대한 정보를 포함하여 여러 데이터 항목을 포함하는 간단한 항목을 제공합니다:
class TreeItem
{
public:
explicit TreeItem(QVariantList data, TreeItem *parent = nullptr);
TreeItem *child(int number);
int childCount() const;
int columnCount() const;
QVariant data(int column) const;
bool insertChildren(int position, int count, int columns);
bool insertColumns(int position, int columns);
TreeItem *parent();
bool removeChildren(int position, int count);
bool removeColumns(int position, int columns);
int row() const;
bool setData(int column, const QVariant &value);
private:
std::vector<std::unique_ptr<TreeItem>> m_childItems;
QVariantList itemData;
TreeItem *m_parentItem;
};각 항목에 정보 열 수를 반환하고, 데이터를 읽고 쓰며, 열을 삽입 및 제거하는 함수를 제공함으로써, 이 API가 QAbstractItemModel 에서 제공하는 것과 유사하도록 설계했습니다. 다만, "행(rows)"이 아닌 "자식(children)"을 다루는 함수를 제공함으로써 항목 간의 관계를 명시적으로 나타냅니다.
각 항목은 자식 항목에 대한 포인터 목록, 부모 항목에 대한 포인터, 그리고 모델 내 특정 행의 열에 포함된 정보에 해당하는 QVariant 객체 목록을 포함합니다.
TreeItem 클래스 구현
각 TreeItem 는 데이터 목록과 선택적인 부모 항목을 사용하여 생성됩니다:
TreeItem::TreeItem(QVariantList data, TreeItem *parent)
: itemData(std::move(data)), m_parentItem(parent)
{}초기에는 각 항목에 자식 항목이 없습니다. 자식 항목은 나중에 설명할 ` insertChildren() ` 함수를 사용하여 항목의 내부 ` childItems ` 멤버에 추가됩니다.
자식 항목들은 std::unique_ptr에 저장되어, 항목 자체이 삭제될 때 해당 항목에 추가된 각 자식 항목도 함께 삭제되도록 보장합니다.
각 항목은 부모에 대한 포인터를 저장하므로, ` parent() ` 함수는 다음과 같이 매우 간단합니다:
TreeItem *TreeItem::parent()
{
return m_parentItem;
}세 가지 함수가 항목의 자식에 대한 정보를 제공합니다. ` child() `는 내부 자식 목록에서 특정 자식을 반환합니다:
TreeItem *TreeItem::child(int number)
{
return (number >= 0 && number < childCount())
? m_childItems.at(number).get() : nullptr;
}childCount() 함수는 자식 요소의 총 개수를 반환합니다:
int TreeItem::childCount() const
{
return int(m_childItems.size());
}row() 함수는 자식이 속한 부모의 자식 목록에서 해당 자식의 인덱스를 확인하는 데 사용됩니다. 이 함수는 부모의 childItems 멤버에 직접 접근하여 이 정보를 얻습니다:
int TreeItem::row() const
{
if (!m_parentItem)
return 0;
const auto it = std::find_if(m_parentItem->m_childItems.cbegin(), m_parentItem->m_childItems.cend(),
[this](const std::unique_ptr<TreeItem> &treeItem) {
return treeItem.get() == this;
});
if (it != m_parentItem->m_childItems.cend())
return std::distance(m_parentItem->m_childItems.cbegin(), it);
Q_ASSERT(false); // should not happen
return -1;
}루트 항목에는 부모 항목이 없습니다. 이 항목의 경우, 다른 항목들과 일관성을 유지하기 위해 0을 반환합니다.
columnCount() 함수는 QVariant 객체들의 내부 itemData 목록에 포함된 요소의 개수를 단순히 반환합니다:
int TreeItem::columnCount() const
{
return int(itemData.count());
}데이터는 ` data() ` 함수를 사용하여 검색되며, 이 함수는 ` itemData ` 리스트 내의 해당 요소에 접근합니다:
QVariant TreeItem::data(int column) const
{
return itemData.value(column);
}데이터는 ` setData() ` 함수를 사용하여 설정되며, 이 함수는 모델의 열 값에 해당하는 유효한 목록 인덱스에 대해서만 ` itemData ` 목록에 값을 저장합니다:
bool TreeItem::setData(int column, const QVariant &value)
{
if (column < 0 || column >= itemData.size())
return false;
itemData[column] = value;
return true;
}모델 구현을 더 쉽게 하기 위해, 데이터가 성공적으로 설정되었음을 나타내기 위해 true를 반환합니다.
편집 가능한 모델은 종종 크기 조정이 가능해야 하며, 이를 통해 행과 열을 삽입하거나 제거할 수 있어야 합니다. 모델 내 특정 모델 인덱스 아래에 행을 삽입하면 해당 항목에 새로운 하위 항목이 삽입되는데, 이는 insertChildren() 함수가 처리합니다:
bool TreeItem::insertChildren(int position, int count, int columns)
{
if (position < 0 || position > qsizetype(m_childItems.size()))
return false;
for (int row = 0; row < count; ++row) {
QVariantList data(columns);
m_childItems.insert(m_childItems.cbegin() + position,
std::make_unique<TreeItem>(data, this));
}
return true;
}이를 통해 새 항목이 필요한 열 수만큼 생성되고, 내부 ` childItems ` 목록의 유효한 위치에 삽입되도록 보장합니다. 항목은 ` removeChildren() ` 함수를 사용하여 제거됩니다:
bool TreeItem::removeChildren(int position, int count)
{
if (position < 0 || position + count > qsizetype(m_childItems.size()))
return false;
for (int row = 0; row < count; ++row)
m_childItems.erase(m_childItems.cbegin() + position);
return true;
}앞서 설명한 바와 같이, 열을 삽입하거나 제거하는 함수는 트리 내의 모든 항목에 대해 호출되어야 하므로, 자식 항목을 삽입하거나 제거하는 함수와는 다르게 사용됩니다. 이를 위해 해당 항목의 각 자식 항목에 대해 이 함수를 재귀적으로 호출합니다:
bool TreeItem::insertColumns(int position, int columns)
{
if (position < 0 || position > itemData.size())
return false;
for (int column = 0; column < columns; ++column)
itemData.insert(position, QVariant());
for (auto &child : std::as_const(m_childItems))
child->insertColumns(position, columns);
return true;
}TreeModel 클래스 정의
TreeModel 클래스는 QAbstractItemModel 클래스의 구현체를 제공하며, 편집 및 크기 조정이 가능한 모델에 필요한 인터페이스를 노출합니다.
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
Q_DISABLE_COPY_MOVE(TreeModel)
TreeModel(const QStringList &headers, const QString &data,
QObject *parent = nullptr);
~TreeModel() override;생성자와 소멸자는 이 모델에 특화된 것입니다.
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = {}) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = {}) const override;
int columnCount(const QModelIndex &parent = {}) const override;읽기 전용 트리 모델은 위의 함수들만 제공하면 됩니다. 다음의 공개 함수들은 편집 및 크기 조정을 지원합니다:
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
bool setHeaderData(int section, Qt::Orientation orientation,
const QVariant &value, int role = Qt::EditRole) override;
bool insertColumns(int position, int columns,
const QModelIndex &parent = {}) override;
bool removeColumns(int position, int columns,
const QModelIndex &parent = {}) override;
bool insertRows(int position, int rows,
const QModelIndex &parent = {}) override;
bool removeRows(int position, int rows,
const QModelIndex &parent = {}) override;
private:
void setupModelData(const QList<QStringView> &lines);
TreeItem *getItem(const QModelIndex &index) const;
std::unique_ptr<TreeItem> rootItem;
};이 예제를 단순화하기 위해, 모델이 노출하는 데이터는 모델의 setupModelData() 함수를 통해 데이터 구조로 정리됩니다. 실제 많은 모델은 원시 데이터를 전혀 처리하지 않고, 단순히 기존 데이터 구조나 라이브러리 API를 활용하여 작동합니다.
TreeModel 클래스 구현
생성자는 루트 항목을 생성하고, 제공된 헤더 데이터로 이를 초기화합니다:
TreeModel::TreeModel(const QStringList &headers, const QString &data, QObject *parent)
: QAbstractItemModel(parent)
{
QVariantList rootData;
for (const QString &header : headers)
rootData << header;
rootItem = std::make_unique<TreeItem>(rootData);
setupModelData(QStringView{data}.split(u'\n'));
}내부 함수인 setupModelData() 를 호출하여 제공된 텍스트 데이터를 모델에서 사용할 수 있는 데이터 구조로 변환합니다. 다른 모델들은 미리 준비된 데이터 구조로 초기화되거나, 자체 데이터를 관리하는 라이브러리의 API를 사용할 수도 있습니다.
TreeModel::~TreeModel() = default;소멸자는 루트 항목만 삭제하면 되며, 그러면 모든 자식 항목이 재귀적으로 삭제됩니다. 루트 항목은 unique_ptr 내에 저장되므로, 이는 기본 소멸자에 의해 자동으로 수행됩니다.
모델이 다른 모델/뷰 구성 요소와 통신하는 인터페이스는 모델 인덱스를 기반으로 하며, 내부 데이터 구조는 항목(item) 기반이므로, 모델에서 구현된 많은 함수는 주어진 모델 인덱스를 해당 항목으로 변환할 수 있어야 합니다. 편의성과 일관성을 위해, 이러한 반복적인 작업을 수행하기 위해 ` getItem() ` 함수를 정의했습니다:
TreeItem *TreeModel::getItem(const QModelIndex &index) const
{
if (index.isValid()) {
if (auto *item = static_cast<TreeItem*>(index.internalPointer()))
return item;
}
return rootItem.get();
}이 함수에 전달되는 각 모델 인덱스는 메모리 내의 유효한 항목에 해당해야 합니다. 인덱스가 유효하지 않거나, 해당 인덱스의 내부 포인터가 유효한 항목을 가리키지 않는 경우, 대신 루트 항목이 반환됩니다.
모델의 ` rowCount() ` 구현은 간단합니다. 먼저 ` getItem() ` 함수를 사용하여 관련 항목을 얻은 다음, 해당 항목이 포함하고 있는 자식 항목의 수를 반환합니다:
int TreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid() && parent.column() > 0)
return 0;
const TreeItem *parentItem = getItem(parent);
return parentItem ? parentItem->childCount() : 0;
}반면, ` columnCount() ` 구현은 모든 항목에 동일한 수의 열이 연관되어 있도록 정의되어 있기 때문에 특정 항목을 찾을 필요가 없습니다.
int TreeModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return rootItem->columnCount();
}그 결과, 열의 개수는 루트 항목에서 직접 얻을 수 있습니다.
항목을 편집하고 선택할 수 있도록 하려면, ` flags() ` 함수를 구현하여 ` Qt::ItemIsEditable ` 및 ` Qt::ItemIsSelectable ` 플래그와 ` Qt::ItemIsEnabled` 플래그를 포함한 플래그 조합을 반환해야 합니다:
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}모델은 다른 구성 요소가 해당 모델의 구조에 대한 데이터와 정보를 요청할 수 있도록 모델 인덱스를 생성할 수 있어야 합니다. 이 작업은 index() 함수에 의해 수행되며, 이 함수는 주어진 부모 항목의 자식 항목에 해당하는 모델 인덱스를 얻기 위해 사용됩니다:
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (parent.isValid() && parent.column() != 0)
return {};이 모델에서는 부모 인덱스가 유효하지 않은 경우(루트 항목에 해당)나 열 번호가 0인 경우에만 자식 항목에 대한 모델 인덱스를 반환합니다.
사용자 정의 getItem() 함수를 사용하여 제공된 모델 인덱스에 해당하는 ` TreeItem ` 인스턴스를 얻고, 지정된 행에 해당하는 자식 항목을 요청합니다.
TreeItem *parentItem = getItem(parent);
if (!parentItem)
return {};
if (auto *childItem = parentItem->child(row))
return createIndex(row, column, childItem);
return {};
}각 항목은 데이터 행 전체에 대한 정보를 포함하고 있으므로, 행 번호와 열 번호, 그리고 항목에 대한 포인터를 인수로 전달하여 ` createIndex()`를 호출함으로써 해당 항목을 고유하게 식별할 수 있는 모델 인덱스를 생성합니다. data() 함수에서는 항목 포인터와 열 번호를 사용하여 모델 인덱스와 연관된 데이터에 접근합니다. 이 모델에서는 데이터를 식별하는 데 행 번호가 필요하지 않습니다.
parent() 함수는 주어진 모델 인덱스에 해당하는 항목을 찾은 다음, parent() 함수를 사용하여 해당 항목의 부모 항목을 얻은 뒤, 부모를 나타내는 모델 인덱스를 생성함으로써 항목의 부모에 대한 모델 인덱스를 제공합니다. ( 위의 다이어그램 참조).
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return {};
TreeItem *childItem = getItem(index);
TreeItem *parentItem = childItem ? childItem->parent() : nullptr;
return (parentItem != rootItem.get() && parentItem != nullptr)
? createIndex(parentItem->row(), 0, parentItem) : QModelIndex{};
}루트 항목을 포함하여 부모가 없는 항목의 경우, null 모델 인덱스를 반환하여 처리합니다. 그 외의 경우에는 index() 함수와 마찬가지로 모델 인덱스가 생성되어 반환되는데, 적절한 행 번호가 부여되지만 index() 구현에서 사용된 방식과 일관성을 유지하기 위해 열 번호는 0으로 설정됩니다.
모델 테스트
QAbstractItemModelTester 항목 모델을 올바르게 구현하는 것은 어려울 수 있습니다. Qt Test 모듈의 `xml-ph-0000@deepl.internal` 클래스는 모델 인덱스 생성 및 부모-자식 관계와 같은 모델의 일관성을 검사합니다.
예를 들어 Qt Test 단위 테스트의 일환으로, 모델 인스턴스를 클래스 생성자에 전달하기만 하면 모델을 테스트할 수 있습니다:
class TestEditableTreeModel : public QObject
{
Q_OBJECT
private slots:
void testTreeModel();
};
void TestEditableTreeModel::testTreeModel()
{
constexpr auto fileName = ":/default.txt"_L1;
QFile file(fileName);
QVERIFY2(file.open(QIODevice::ReadOnly | QIODevice::Text),
qPrintable(fileName + " cannot be opened: "_L1 + file.errorString()));
const QStringList headers{"column1"_L1, "column2"_L1};
TreeModel model(headers, QString::fromUtf8(file.readAll()));
QAbstractItemModelTester tester(&model);
}
QTEST_APPLESS_MAIN(TestEditableTreeModel)
#include "test.moc"ctest 실행 파일을 사용하여 실행할 수 있는 테스트를 생성하려면 add_test() 를 사용합니다:
# Unit Test
include(CTest)
qt_add_executable(editabletreemodel_tester
test.cpp
treeitem.cpp treeitem.h
treemodel.cpp treemodel.h)
target_link_libraries(editabletreemodel_tester PRIVATE
Qt6::Core
Qt6::Test
)
if(ANDROID)
target_link_libraries(editabletreemodel_tester PRIVATE
Qt6::Gui
)
endif()
qt_add_resources(editabletreemodel_tester "editabletreemodel_tester"
PREFIX
"/"
FILES
${editabletreemodel_resource_files}
)
add_test(NAME editabletreemodel_tester
COMMAND editabletreemodel_tester)© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.



