Sur cette page

Exemple de modèle d'arborescence modifiable

Cet exemple montre comment implémenter un modèle d'arborescence simple basé sur des éléments, pouvant être utilisé avec d'autres classes du framework modèle/vue.

Application illustrant la modification d'un modèle arborescent

Le modèle prend en charge les éléments modifiables, les en-têtes personnalisés, ainsi que la possibilité d'insérer et de supprimer des lignes et des colonnes. Grâce à ces fonctionnalités, il est également possible d'insérer de nouveaux éléments enfants, comme le montre le code d'exemple fourni.

Présentation générale

Comme décrit dans la référence sur la sous-classification des modèles, les modèles doivent fournir des implémentations pour l'ensemble standard de fonctions de modèle : flags(), data(), headerData(), columnCount() et rowCount(). De plus, les modèles hiérarchiques, tels que celui-ci, doivent fournir des implémentations de index() et parent().

Un modèle modifiable doit fournir des implémentations de setData() et setHeaderData(), et doit renvoyer une combinaison appropriée d’indicateurs depuis sa fonction flags().

Comme cet exemple permet de modifier les dimensions du modèle, nous devons également implémenter les fonctions insertRows(), insertColumns(), removeRows() et removeColumns().

Conception

Comme dans l’exemple du modèle d’arbre simple, le modèle sert simplement d’enveloppe à un ensemble d’instances d’une classe de type TreeItem. Chaque TreeItem est conçu pour contenir les données d’une ligne d’éléments dans une arborescence ; il contient donc une liste de valeurs correspondant aux données affichées dans chaque colonne.

Étant donné que ` QTreeView ` fournit une vue orientée ligne sur un modèle, il est naturel d’opter pour une conception orientée ligne pour les structures de données qui fourniront des données via un modèle à ce type de vue. Bien que cela rende le modèle arborescent moins flexible, et peut-être moins utile pour une utilisation avec des vues plus sophistiquées, cela simplifie sa conception et facilite sa mise en œuvre.

Schéma illustrant les relations entre les éléments racine, parents et enfantsRelations entre les éléments internes

Lors de la conception d’une structure de données destinée à être utilisée avec un modèle personnalisé, il est utile d’exposer le parent de chaque élément via une fonction telle que `TreeItem::parent()`, car cela facilitera l’écriture de la fonction ` parent()` propre au modèle. De même, une fonction telle que `TreeItem::child()` s’avère utile lors de l’implémentation de la fonction ` index()` du modèle. Ainsi, chaque ` TreeItem ` conserve des informations sur son parent et ses enfants, ce qui nous permet de parcourir la structure arborescente.

Le diagramme montre comment les instances de ` TreeItem ` sont reliées via leurs fonctions `parent()` et `child() `.

Dans l’exemple présenté, deux éléments de niveau supérieur, A et B, peuvent être obtenus à partir de l’élément racine en appelant sa fonction child(), et chacun de ces éléments renvoie le nœud racine via sa fonction parent(), bien que cela ne soit illustré que pour l’élément A.

Chaque élément « TreeItem » stocke les données de chaque colonne de la ligne qu’il représente dans son membre privé « itemData » (une liste d’objets « QVariant »). Étant donné qu’il existe une correspondance biunivoque entre chaque colonne de la vue et chaque entrée de la liste, nous fournissons une fonction « data() » simple pour lire les entrées de la liste « itemData » et une fonction « setData() » permettant de les modifier. Comme pour les autres fonctions de l’élément, cela simplifie l’implémentation des fonctions ` data()` et ` setData()` du modèle.

Nous plaçons un élément à la racine de l’arborescence des éléments. Cet élément racine correspond à l’index de modèle null, ` QModelIndex()`, qui est utilisé pour représenter le parent d’un élément de niveau supérieur lors de la gestion des index de modèle. Bien que l’élément racine n’ait pas de représentation visible dans aucune des vues standard, nous utilisons sa liste interne d’objets ` QVariant ` pour stocker une liste de chaînes de caractères qui seront transmises aux vues afin d’être utilisées comme titres d’en-têtes horizontaux.

Schéma du modèle arborescent avec index de lignes et de colonnesAccès aux données via le modèle

Dans le cas illustré par le schéma, l’information représentée par a peut être obtenue à l’aide de l’API standard modèle/vue :

QVariant a = model->index(0, 0, QModelIndex()).data();

Étant donné que chaque élément contient des données pour chaque colonne d'une ligne donnée, il peut exister plusieurs index de modèle correspondant au même objet ` TreeItem `. Par exemple, l'information représentée par « b » peut être obtenue à l'aide du code suivant :

QVariant b = model->index(1, 0, QModelIndex()).data();

On accéderait au même objet « TreeItem » sous-jacent pour obtenir les informations relatives aux autres index de modèle situés dans la même ligne que b.

Dans la classe de modèle, ` TreeModel`, nous associons les objets ` TreeItem ` aux index de modèle en transmettant un pointeur pour chaque élément lorsque nous créons l’index de modèle correspondant à l’aide de ` QAbstractItemModel::createIndex()` dans nos implémentations des méthodes `index() ` et `parent()`. Nous pouvons récupérer les pointeurs ainsi stockés en appelant la fonction internalPointer() sur l’index de modèle concerné ; nous créons notre propre fonction getItem() pour effectuer cette tâche à notre place, et nous l’appelons depuis nos implémentations des méthodes data() et parent().

Le stockage de pointeurs vers des éléments est pratique lorsque nous contrôlons la manière dont ils sont créés et détruits, car nous pouvons supposer qu’une adresse obtenue via ` internalPointer()` est un pointeur valide. Cependant, certains modèles doivent gérer des éléments provenant d’autres composants d’un système, et dans de nombreux cas, il n’est pas possible de contrôler entièrement la manière dont les éléments sont créés ou détruits. Dans de telles situations, une approche purement basée sur les pointeurs doit être complétée par des mécanismes de sécurité afin de garantir que le modèle ne tente pas d’accéder à des éléments qui ont été supprimés.

Stockage des informations dans la structure de données sous-jacente

Plusieurs données sont stockées sous forme d’objets ` QVariant ` dans le membre ` itemData ` de chaque instance de ` TreeItem `.

Le diagramme montre comment les éléments d’information, représentés par les étiquettes a, b et c dans les deux diagrammes précédents, sont stockés dans les éléments A, B et C de la structure de données sous-jacente. Notez que les informations provenant d’une même ligne du modèle sont toutes extraites du même élément. Chaque élément d’une liste correspond à une information exposée par chaque colonne d’une ligne donnée du modèle.

Schéma d'une structure de données possible utilisant QVariant

L’implémentation de l’ TreeModel, ayant été conçue pour être utilisée avec QTreeView, nous avons ajouté une contrainte concernant l’utilisation des instances de TreeItem: chaque élément doit exposer le même nombre de colonnes de données. Cela garantit la cohérence de l’affichage du modèle, ce qui nous permet d’utiliser l’élément racine pour déterminer le nombre de colonnes de n’importe quelle ligne donnée, et impose simplement de créer des éléments contenant suffisamment de données pour le nombre total de colonnes. Par conséquent, l’insertion et la suppression de colonnes sont des opérations chronophages, car nous devons parcourir l’arborescence dans son intégralité pour modifier chaque élément.

Une autre approche consisterait à concevoir la classe ` TreeModel ` de manière à ce qu’elle tronque ou étende la liste des données dans chaque instance d’` TreeItem ` à mesure que les éléments de données sont modifiés. Cependant, cette approche de redimensionnement « paresseux » ne nous permettrait d’insérer et de supprimer des colonnes qu’à la fin de chaque ligne et ne permettrait pas d’insérer ou de supprimer des colonnes à des positions arbitraires au sein de chaque ligne.

Schéma illustrant l'accès au modèle arborescent à l'aide d'index de modèleMise en relation d’éléments à l’aide d’index de modèle

Comme dans l’exemple du modèle d’arbre simple, l’ TreeModel doit être capable de prendre un index de modèle, de trouver l’ TreeItem correspondant et de renvoyer les index de modèle correspondant à ses parents et à ses enfants.

Dans le diagramme, nous montrons comment l’implémentation de la méthode `parent() ` du modèle obtient l’index de modèle correspondant au parent d’un élément fourni par l’appelant, en utilisant les éléments présentés dans un diagramme précédent.

Un pointeur vers l’élément C est obtenu à partir de l’index de modèle correspondant à l’aide de la fonction ` QModelIndex::internalPointer()`. Ce pointeur a été stocké en interne dans l’index lors de sa création. Étant donné que l’enfant contient un pointeur vers son parent, nous utilisons sa fonction `parent()` pour obtenir un pointeur vers l’élément B. L’index du modèle parent est créé à l’aide de la fonction ` QAbstractItemModel::createIndex()`, en passant le pointeur vers l’élément B comme pointeur interne.

Définition de la classe `TreeItem`

La classe ` TreeItem ` fournit des éléments simples contenant plusieurs données, notamment des informations sur leurs éléments parents et enfants :

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;
};

Nous avons conçu l’API de manière à ce qu’elle soit similaire à celle fournie par QAbstractItemModel, en dotant chaque élément de fonctions permettant de renvoyer le nombre de colonnes d’informations, de lire et d’écrire des données, ainsi que d’insérer et de supprimer des colonnes. Cependant, nous rendons la relation entre les éléments explicite en fournissant des fonctions permettant de gérer les « enfants » plutôt que les « lignes ».

Chaque élément contient une liste de pointeurs vers des éléments enfants, un pointeur vers son élément parent, ainsi qu’une liste d’objets ` QVariant ` correspondant aux informations contenues dans les colonnes d’une ligne donnée du modèle.

Implémentation de la classe `TreeItem`

Chaque objet ` TreeItem ` est instancié avec une liste de données et un élément parent facultatif :

TreeItem::TreeItem(QVariantList data, TreeItem *parent)
    : itemData(std::move(data)), m_parentItem(parent)
{}

Au départ, chaque élément n'a pas d'enfants. Ceux-ci sont ajoutés au membre interne ` childItems ` de l'élément à l'aide de la fonction ` insertChildren() `, décrite plus loin.

Les enfants sont stockés dans des objets `std::unique_ptr` afin de garantir que chaque enfant ajouté à l'élément soit supprimé lorsque l'élément lui-même est supprimé.

Comme chaque élément stocke un pointeur vers son parent, la fonction ` parent() ` est triviale :

TreeItem *TreeItem::parent()
{
    return m_parentItem;
}

Trois fonctions fournissent des informations sur les enfants d’un élément. La fonction ` child() ` renvoie un enfant spécifique issu de la liste interne des enfants :

TreeItem *TreeItem::child(int number)
{
    return (number >= 0 && number < childCount())
        ? m_childItems.at(number).get() : nullptr;
}

La fonction childCount() renvoie le nombre total d'enfants :

int TreeItem::childCount() const
{
    return int(m_childItems.size());
}

La fonction ` row() ` sert à déterminer l’index d’un enfant dans la liste des enfants de son parent. Elle accède directement au membre ` childItems ` du parent pour obtenir cette information :

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;
}

L'élément racine n'a pas d'élément parent ; pour cet élément, nous renvoyons zéro afin d'assurer la cohérence avec les autres éléments.

La fonction ` columnCount() ` renvoie simplement le nombre d’éléments de la liste interne ` itemData ` des objets ` QVariant ` :

int TreeItem::columnCount() const
{
    return int(itemData.count());
}

Les données sont récupérées à l’aide de la fonction ` data() `, qui accède à l’élément approprié de la liste ` itemData ` :

QVariant TreeItem::data(int column) const
{
    return itemData.value(column);
}

Les données sont définies à l'aide de la fonction ` setData() `, qui stocke uniquement les valeurs dans la liste ` itemData ` pour les indices de liste valides, correspondant aux valeurs des colonnes du modèle :

bool TreeItem::setData(int column, const QVariant &value)
{
    if (column < 0 || column >= itemData.size())
        return false;

    itemData[column] = value;
    return true;
}

Pour faciliter la mise en œuvre du modèle, nous renvoyons « true » pour indiquer que les données ont été définies avec succès.

Les modèles modifiables doivent souvent être redimensionnables, ce qui permet d’insérer et de supprimer des lignes et des colonnes. L’insertion de lignes sous un index donné du modèle entraîne l’insertion de nouveaux éléments enfants dans l’élément correspondant, gérée par la fonction ` 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;
}

Cela garantit que les nouveaux éléments sont créés avec le nombre de colonnes requis et insérés à une position valide dans la liste interne ` childItems `. Les éléments sont supprimés à l'aide de la fonction ` 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;
}

Comme indiqué plus haut, les fonctions d’insertion et de suppression de colonnes s’utilisent différemment de celles d’insertion et de suppression d’éléments enfants, car elles doivent être appelées sur chaque élément de l’arborescence. Pour ce faire, nous appelons cette fonction de manière récursive sur chaque enfant de l’élément :

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;
}

Définition de la classe `TreeModel`

La classe ` TreeModel ` fournit une implémentation de la classe ` QAbstractItemModel `, en exposant l’interface nécessaire à un modèle pouvant être modifié et redimensionné.

class TreeModel : public QAbstractItemModel
{
    Q_OBJECT

public:
    Q_DISABLE_COPY_MOVE(TreeModel)

    TreeModel(const QStringList &headers, const QString &data,
              QObject *parent = nullptr);
    ~TreeModel() override;

Le constructeur et le destructeur sont spécifiques à ce modèle.

    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;

Les modèles d'arborescence en lecture seule doivent uniquement fournir les fonctions ci-dessus. Les fonctions publiques suivantes permettent la modification et le redimensionnement :

    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;
};

Pour simplifier cet exemple, les données exposées par le modèle sont organisées en une structure de données par la fonction setupModelData() du modèle. De nombreux modèles réels ne traitent pas du tout les données brutes, mais fonctionnent simplement avec une structure de données existante ou l'API d'une bibliothèque.

Implémentation de la classe TreeModel

Le constructeur crée un élément racine et l'initialise avec les données d'en-tête fournies :

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'));
}

Nous appelons la fonction interne `setupModelData()` pour convertir les données textuelles fournies en une structure de données utilisable avec le modèle. D'autres modèles peuvent être initialisés à l'aide d'une structure de données prête à l'emploi, ou utiliser une API issue d'une bibliothèque qui gère ses propres données.

TreeModel::~TreeModel() = default;

Le destructeur n’a qu’à supprimer l’élément racine, ce qui entraînera la suppression récursive de tous les éléments enfants. Cette opération est effectuée automatiquement par le destructeur par défaut, puisque l’élément racine est stocké dans un `unique_ptr`.

Étant donné que l’interface du modèle avec les autres composants modèle/vue repose sur des index de modèle, et que la structure de données interne est basée sur des éléments, bon nombre des fonctions implémentées par le modèle doivent être capables de convertir n’importe quel index de modèle donné en son élément correspondant. Par souci de commodité et de cohérence, nous avons défini une fonction ` getItem() ` pour effectuer cette tâche répétitive :

TreeItem *TreeModel::getItem(const QModelIndex &index) const
{
    if (index.isValid()) {
        if (auto *item = static_cast<TreeItem*>(index.internalPointer()))
            return item;
    }
    return rootItem.get();
}

Chaque index de modèle transmis à cette fonction doit correspondre à un élément valide en mémoire. Si l’index n’est pas valide, ou si son pointeur interne ne pointe pas vers un élément valide, l’élément racine est renvoyé à la place.

L’implémentation de la fonction ` rowCount() ` du modèle est simple : elle utilise d’abord la fonction ` getItem() ` pour obtenir l’élément concerné, puis elle renvoie le nombre d’enfants qu’il contient :

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;
}

En revanche, l’implémentation de ` columnCount() ` n’a pas besoin de rechercher un élément particulier, car tous les éléments sont définis pour avoir le même nombre de colonnes qui leur sont associées.

int TreeModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return rootItem->columnCount();
}

Par conséquent, le nombre de colonnes peut être obtenu directement à partir de l’élément racine.

Pour permettre la modification et la sélection des éléments, la fonction ` flags() ` doit être implémentée de manière à renvoyer une combinaison d’indicateurs comprenant les indicateurs ` Qt::ItemIsEditable ` et ` Qt::ItemIsSelectable `, ainsi que ` Qt::ItemIsEnabled` :

Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::NoItemFlags;

    return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}

Le modèle doit être capable de générer des index de modèle afin de permettre à d’autres composants de demander des données et des informations sur sa structure. Cette tâche est effectuée par la fonction ` index() `, qui sert à obtenir les index de modèle correspondant aux enfants d’un élément parent donné :

QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
    if (parent.isValid() && parent.column() != 0)
        return {};

Dans ce modèle, nous ne renvoyons les index des éléments enfants que si l’index parent n’est pas valide (ce qui correspond à l’élément racine) ou s’il possède un numéro de colonne égal à zéro.

Nous utilisons la fonction personnalisée `getItem()` pour obtenir une instance de ` TreeItem ` correspondant à l’index de modèle fourni, et demandons son élément enfant correspondant à la ligne spécifiée.

    TreeItem *parentItem = getItem(parent);
    if (!parentItem)
        return {};

    if (auto *childItem = parentItem->child(row))
        return createIndex(row, column, childItem);
    return {};
}

Comme chaque élément contient les informations d’une ligne entière de données, nous créons un index de modèle pour l’identifier de manière unique en appelant la fonction ` createIndex()` avec les numéros de ligne et de colonne ainsi qu’un pointeur vers l’élément. Dans la fonction data(), nous utiliserons le pointeur vers l’élément et le numéro de colonne pour accéder aux données associées à l’index de modèle ; dans ce modèle, le numéro de ligne n’est pas nécessaire pour identifier les données.

La fonction ` parent() ` fournit les index de modèle des éléments parents en recherchant l’élément correspondant à un index de modèle donné, en utilisant sa fonction `parent()` pour obtenir son élément parent, puis en créant un index de modèle pour représenter ce parent. (Voir le schéma ci-dessus).

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{};
}

Les éléments sans parent, y compris l’élément racine, sont gérés en renvoyant un index de modèle nul. Dans le cas contraire, un index de modèle est créé et renvoyé comme dans la fonction `index()`, avec un numéro de ligne approprié, mais avec un numéro de colonne égal à zéro afin d’être cohérent avec le schéma utilisé dans l’implémentation de `index()`.

Test du modèle

La mise en œuvre correcte d’un modèle d’éléments peut s’avérer complexe. La classe QAbstractItemModelTester du Qt Test module vérifie la cohérence du modèle, notamment la création de l’index du modèle et les relations parent-enfant.

Vous pouvez tester votre modèle en transmettant simplement une instance de modèle au constructeur de la classe, par exemple dans le cadre d'un test unitaire 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"

Pour créer un test pouvant être exécuté à l'aide de l'exécutable ctest, on utilise ` 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)

Exemple de projet sur code.qt.io

© 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.