Beispiel für ein editierbares Baummodell
Dieses Beispiel zeigt, wie man ein einfaches, auf Elementen basierendes Baummodell implementiert, das mit anderen Klassen im Model/View-Framework verwendet werden kann.
Das Modell unterstützt editierbare Elemente, benutzerdefinierte Überschriften und die Möglichkeit, Zeilen und Spalten einzufügen und zu entfernen. Mit diesen Funktionen ist es auch möglich, neue untergeordnete Elemente einzufügen, was in dem unterstützenden Beispielcode gezeigt wird.
Übersicht
Wie in der Model Subclassing Reference beschrieben, müssen Modelle Implementierungen für den Standardsatz von Modellfunktionen bereitstellen: flags(), data(), headerData(), columnCount() und rowCount(). Darüber hinaus müssen hierarchische Modelle, wie das vorliegende, Implementierungen von index() und parent() bereitstellen.
Ein editierbares Modell muss Implementierungen von setData() und setHeaderData() bereitstellen und muss eine geeignete Kombination von Flags von seiner flags() Funktion zurückgeben.
Da in diesem Beispiel die Abmessungen des Modells geändert werden können, müssen wir auch insertRows(), insertColumns(), removeRows() und removeColumns() implementieren.
Entwurf
Wie beim Simple Tree Model-Beispiel fungiert das Modell einfach als Wrapper für eine Sammlung von Instanzen der Klasse TreeItem
. Jedes TreeItem
ist so konzipiert, dass es Daten für eine Zeile von Elementen in einer Baumansicht enthält, also eine Liste von Werten, die den in jeder Spalte angezeigten Daten entsprechen.
Da QTreeView eine zeilenorientierte Sicht auf ein Modell bietet, ist es naheliegend, ein zeilenorientiertes Design für Datenstrukturen zu wählen, die Daten über ein Modell an diese Art von Sicht liefern sollen. Obwohl dies das Baummodell weniger flexibel und möglicherweise weniger nützlich für die Verwendung mit anspruchsvolleren Ansichten macht, ist es weniger komplex im Design und einfacher zu implementieren.
![]() | Beziehungen zwischen internen Elementen Wenn man eine Datenstruktur für die Verwendung mit einem benutzerdefinierten Model entwirft, ist es nützlich, die Eltern eines jeden Elements über eine Funktion wie TreeItem::parent() offenzulegen, weil es das Schreiben der eigenen parent() Funktion des Models einfacher macht. In ähnlicher Weise ist eine Funktion wie TreeItem::child() hilfreich bei der Implementierung der index() Funktion des Models. Das Ergebnis ist, dass jedes Das Diagramm zeigt, wie In dem gezeigten Beispiel können zwei Elemente der obersten Ebene, A und B, vom Wurzelelement durch Aufruf seiner child()-Funktion erhalten werden, und jedes dieser Elemente gibt den Wurzelknoten von seinen parent()-Funktionen zurück, obwohl dies nur für Element A gezeigt wird. |
Jedes TreeItem
speichert Daten für jede Spalte in der Zeile, die es darstellt, in seinem privaten Mitglied itemData
(eine Liste von QVariant Objekten). Da es eine Eins-zu-Eins-Zuordnung zwischen jeder Spalte in der Ansicht und jedem Eintrag in der Liste gibt, stellen wir eine einfache data() -Funktion zur Verfügung, um die Einträge in der itemData
Liste zu lesen, und eine setData() -Funktion, um ihre Änderung zu ermöglichen. Wie bei anderen Funktionen im Element vereinfacht dies die Implementierung der Funktionen data() und setData() des Modells.
Wir platzieren ein Element an der Wurzel des Baums der Elemente. Dieses Wurzelelement entspricht dem Nullmodell-Index QModelIndex(), der bei der Handhabung von Modellindizes zur Darstellung des übergeordneten Elements auf oberster Ebene verwendet wird. Obwohl das Wurzelelement in keiner der Standardansichten eine sichtbare Darstellung hat, verwenden wir seine interne Liste von QVariant -Objekten, um eine Liste von Zeichenketten zu speichern, die an die Ansichten zur Verwendung als horizontale Überschriften übergeben werden.
![]() | Zugriff auf Daten über das Modell In dem im Diagramm dargestellten Fall kann die durch a dargestellte Information über die Standard-API für Modelle und Ansichten abgerufen werden: QVariant a = model->index(0, 0, QModelIndex()).data(); Da jedes Element Daten für jede Spalte in einer bestimmten Zeile enthält, kann es viele Modellindizes geben, die auf dasselbe QVariant b = model->index(1, 0, QModelIndex()).data(); Auf dieselbe zugrunde liegende |
In der Modellklasse TreeModel
verknüpfen wir TreeItem
Objekte mit Modellindizes, indem wir für jedes Element einen Zeiger übergeben, wenn wir den entsprechenden Modellindex mit QAbstractItemModel::createIndex() in unseren index()- und parent()-Implementierungen erstellen. Wir können auf diese Weise gespeicherte Zeiger abrufen, indem wir die Funktion internalPointer() für den entsprechenden Modellindex aufrufen - wir erstellen unsere eigene getItem() -Funktion, um die Arbeit für uns zu erledigen, und rufen sie von unseren data()- und parent()-Implementierungen aus auf.
Das Speichern von Zeigern auf Elemente ist praktisch, wenn wir kontrollieren, wie sie erstellt und zerstört werden, da wir davon ausgehen können, dass eine von internalPointer() erhaltene Adresse ein gültiger Zeiger ist. Einige Modelle müssen jedoch Elemente behandeln, die von anderen Komponenten in einem System bezogen werden, und in vielen Fällen ist es nicht möglich, vollständig zu kontrollieren, wie Elemente erstellt oder zerstört werden. In solchen Situationen muss ein reiner zeigerbasierter Ansatz durch Sicherheitsvorkehrungen ergänzt werden, um sicherzustellen, dass das Modell nicht versucht, auf Elemente zuzugreifen, die gelöscht wurden.
Speichern von Informationen in der zugrunde liegenden Datenstruktur Mehrere Daten werden als QVariant Objekte im Das Diagramm zeigt, wie Informationen, die in den beiden vorangegangenen Diagrammen durch die Bezeichnungen a, b und c dargestellt wurden, in den Elementen A, B und C in der zugrunde liegenden Datenstruktur gespeichert werden. Beachten Sie, dass Informationen aus derselben Zeile des Modells alle aus demselben Element stammen. Jedes Element in einer Liste entspricht einer Information, die in jeder Spalte einer bestimmten Zeile des Modells enthalten ist. | ![]() |
Da die Implementierung von TreeModel
für die Verwendung mit QTreeView konzipiert wurde, haben wir eine Einschränkung für die Verwendung von TreeItem
Instanzen hinzugefügt: Jedes Element muss die gleiche Anzahl von Datenspalten aufweisen. Dadurch wird die Anzeige des Modells konsistent, da wir das Root-Element verwenden können, um die Anzahl der Spalten für jede beliebige Zeile zu bestimmen, und nur die Anforderung hinzufügen, dass wir Elemente erstellen, die genügend Daten für die Gesamtzahl der Spalten enthalten. Folglich ist das Einfügen und Entfernen von Spalten zeitaufwändig, da wir den gesamten Baum durchlaufen müssen, um jedes Element zu ändern.
Ein alternativer Ansatz wäre, die Klasse TreeModel
so zu gestalten, dass sie die Liste der Daten in den einzelnen Instanzen von TreeItem
abschneidet oder erweitert, wenn Datenelemente geändert werden. Dieser "träge" Ansatz zur Größenänderung würde jedoch nur das Einfügen und Entfernen von Spalten am Ende jeder Zeile ermöglichen, nicht aber das Einfügen oder Entfernen von Spalten an beliebigen Positionen in jeder Zeile.
![]() | Verknüpfung von Elementen mit Modellindizes Wie im Beispiel des einfachen Baummodells muss Im Diagramm wird gezeigt, wie die parent()-Implementierung des Modells den Modellindex ermittelt, der dem übergeordneten Element eines vom Aufrufer gelieferten Elements entspricht, wobei die in einem früheren Diagramm dargestellten Elemente verwendet werden. Ein Zeiger auf das Element C wird mit Hilfe der Funktion QModelIndex::internalPointer() aus dem entsprechenden Modellindex ermittelt. Der Zeiger wurde intern im Index gespeichert, als dieser erstellt wurde. Da das untergeordnete Element einen Zeiger auf sein übergeordnetes Element enthält, verwenden wir seine Funktion parent(), um einen Zeiger auf Element B zu erhalten. Der übergeordnete Modellindex wird mit der Funktion QAbstractItemModel::createIndex() erstellt, wobei der Zeiger auf Element B als interner Zeiger übergeben wird. |
TreeItem Klassendefinition
Die Klasse TreeItem
bietet einfache Elemente, die mehrere Daten enthalten, einschließlich Informationen über ihre Eltern- und Kindelemente:
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; };
Wir haben die API so gestaltet, dass sie der von QAbstractItemModel ähnelt, indem wir jedem Element Funktionen geben, die die Anzahl der Spalten mit Informationen zurückgeben, Daten lesen und schreiben sowie Spalten einfügen und entfernen. Wir machen jedoch die Beziehung zwischen Elementen explizit, indem wir Funktionen zur Verfügung stellen, die mit "Kindern" statt mit "Zeilen" arbeiten.
Jedes Element enthält eine Liste von Zeigern auf untergeordnete Elemente, einen Zeiger auf sein übergeordnetes Element und eine Liste von QVariant Objekten, die den Informationen in den Spalten einer bestimmten Zeile des Modells entsprechen.
TreeItem Klasse Implementierung
Jedes TreeItem
wird mit einer Liste von Daten und einem optionalen übergeordneten Element erstellt:
TreeItem::TreeItem(QVariantList data, TreeItem *parent) : itemData(std::move(data)), m_parentItem(parent) {}
Anfänglich hat jeder Eintrag keine Kinder. Diese werden mit der später beschriebenen Funktion insertChildren()
dem internen Element childItems
hinzugefügt.
Die Kinder werden in std::unique_ptr gespeichert, um sicherzustellen, dass jedes Kind, das dem Element hinzugefügt wird, gelöscht wird, wenn das Element selbst gelöscht wird.
Da jedes Element einen Zeiger auf sein Elternteil speichert, ist die Funktion parent()
trivial:
TreeItem *TreeItem::parent() { return m_parentItem; }
Drei Funktionen liefern Informationen über die Kinder eines Eintrags. child()
gibt ein bestimmtes Kind aus der internen Liste der Kinder zurück:
TreeItem *TreeItem::child(int number) { return (number >= 0 && number < childCount()) ? m_childItems.at(number).get() : nullptr; }
Die Funktion childCount()
gibt die Gesamtzahl der Kinder zurück:
int TreeItem::childCount() const { return int(m_childItems.size()); }
Die Funktion childNumber()
wird verwendet, um den Index des Kindes in der Liste der Kinder des Elternteils zu ermitteln. Sie greift direkt auf das Mitglied childItems
des Elternteils zu, um diese Information zu erhalten:
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; }
Das Stammelement hat kein übergeordnetes Element; für dieses Element geben wir Null zurück, um mit den anderen Elementen konsistent zu sein.
Die Funktion columnCount()
gibt einfach die Anzahl der Elemente in der internen itemData
Liste der QVariant Objekte zurück:
int TreeItem::columnCount() const { return int(itemData.count()); }
Die Daten werden mit der Funktion data()
abgerufen, die auf das entsprechende Element in der Liste itemData
zugreift:
QVariant TreeItem::data(int column) const { return itemData.value(column); }
Das Setzen von Daten erfolgt mit der Funktion setData()
, die nur Werte in der Liste itemData
für gültige Listenindizes speichert, die den Spaltenwerten im Modell entsprechen:
bool TreeItem::setData(int column, const QVariant &value) { if (column < 0 || column >= itemData.size()) return false; itemData[column] = value; return true; }
Um die Implementierung des Modells zu erleichtern, geben wir true zurück, um anzuzeigen, dass die Daten erfolgreich gesetzt wurden.
Bearbeitbare Modelle müssen oft in der Größe veränderbar sein, damit Zeilen und Spalten eingefügt und entfernt werden können. Das Einfügen von Zeilen unter einem bestimmten Modellindex im Modell führt zum Einfügen neuer untergeordneter Elemente in das entsprechende Element, was durch die Funktion insertChildren()
erfolgt:
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; }
Dadurch wird sichergestellt, dass neue Elemente mit der erforderlichen Anzahl von Spalten erstellt und an einer gültigen Position in der internen Liste childItems
eingefügt werden. Elemente werden mit der Funktion removeChildren()
entfernt:
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; }
Wie bereits erwähnt, werden die Funktionen zum Einfügen und Entfernen von Spalten anders verwendet als die Funktionen zum Einfügen und Entfernen von untergeordneten Elementen, da sie für jedes Element im Baum aufgerufen werden sollen. Zu diesem Zweck wird diese Funktion rekursiv für jedes untergeordnete Element des Elements aufgerufen:
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 Klassendefinition
Die Klasse TreeModel
bietet eine Implementierung der Klasse QAbstractItemModel, die die notwendige Schnittstelle für ein Modell bereitstellt, das bearbeitet und in der Größe verändert werden kann.
class TreeModel : public QAbstractItemModel { Q_OBJECT public: Q_DISABLE_COPY_MOVE(TreeModel) TreeModel(const QStringList &headers, const QString &data, QObject *parent = nullptr); ~TreeModel() override;
Der Konstruktor und der Destruktor sind spezifisch für dieses Modell.
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;
Schreibgeschützte Baummodelle müssen nur die oben genannten Funktionen bereitstellen. Die folgenden öffentlichen Funktionen bieten Unterstützung für die Bearbeitung und Größenänderung:
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; };
Um dieses Beispiel zu vereinfachen, werden die vom Modell bereitgestellten Daten mit der Funktion setupModelData() in eine Datenstruktur organisiert. Viele Modelle in der realen Welt werden die Rohdaten überhaupt nicht verarbeiten, sondern einfach mit einer vorhandenen Datenstruktur oder Bibliotheks-API arbeiten.
TreeModel Klasse Implementierung
Der Konstruktor erzeugt ein Wurzelelement und initialisiert es mit den übergebenen Kopfdaten:
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')); }
Wir rufen die interne Funktion setupModelData() auf, um die gelieferten Textdaten in eine Datenstruktur umzuwandeln, die wir mit dem Modell verwenden können. Andere Modelle können mit einer vorgefertigten Datenstruktur initialisiert werden oder eine API aus einer Bibliothek verwenden, die ihre eigenen Daten verwaltet.
TreeModel::~TreeModel() = default;
Der Destruktor muss nur das Wurzelelement löschen, was zur Folge hat, dass alle untergeordneten Elemente rekursiv gelöscht werden. Dies geschieht automatisch durch den Standard-Destruktor, da das Wurzelelement in einer unique_ptr gespeichert ist.
Da die Schnittstelle des Modells zu den anderen Modell-/Ansichtskomponenten auf Modellindizes basiert und die interne Datenstruktur auf Elementen basiert, müssen viele der vom Modell implementierten Funktionen in der Lage sein, jeden beliebigen Modellindex in das entsprechende Element zu konvertieren. Der Einfachheit und Konsistenz halber haben wir eine Funktion getItem()
definiert, die diese sich wiederholende Aufgabe übernimmt:
TreeItem *TreeModel::getItem(const QModelIndex &index) const { if (index.isValid()) { if (auto *item = static_cast<TreeItem*>(index.internalPointer())) return item; } return rootItem.get(); }
Jeder Modellindex, der an diese Funktion übergeben wird, sollte einem gültigen Element im Speicher entsprechen. Wenn der Index ungültig ist oder sein interner Zeiger nicht auf ein gültiges Element verweist, wird stattdessen das Stammelement zurückgegeben.
Die Implementierung des Modells rowCount()
ist einfach: Sie verwendet zunächst die Funktion getItem()
, um das betreffende Element zu erhalten, und gibt dann die Anzahl der Kinder zurück, die es enthält:
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; }
Im Gegensatz dazu braucht die Implementierung von columnCount()
nicht nach einem bestimmten Element zu suchen, da alle Elemente so definiert sind, dass sie dieselbe Anzahl von Spalten haben, die ihnen zugeordnet sind.
int TreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return rootItem->columnCount(); }
Folglich kann die Anzahl der Spalten direkt aus dem Stammelement abgeleitet werden.
Damit Elemente bearbeitet und ausgewählt werden können, muss die Funktion flags()
so implementiert werden, dass sie eine Kombination von Flags zurückgibt, die die Flags Qt::ItemIsEditable und Qt::ItemIsSelectable sowie Qt::ItemIsEnabled umfasst:
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; return Qt::ItemIsEditable | QAbstractItemModel::flags(index); }
Das Modell muss in der Lage sein, Modellindizes zu erzeugen, damit andere Komponenten Daten und Informationen über seine Struktur abfragen können. Diese Aufgabe wird von der Funktion index()
übernommen, die verwendet wird, um Modellindizes zu erhalten, die den Kindern eines bestimmten übergeordneten Elements entsprechen:
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid() && parent.column() != 0) return {};
In diesem Modell werden Modellindizes für untergeordnete Elemente nur dann zurückgegeben, wenn der übergeordnete Index ungültig ist (entsprechend dem Stammelement) oder wenn er eine Nullspaltennummer hat.
Wir verwenden die benutzerdefinierte Funktion getItem(), um eine Instanz von TreeItem
zu erhalten, die dem angegebenen Modellindex entspricht, und fordern das untergeordnete Element an, das der angegebenen Zeile entspricht.
TreeItem *parentItem = getItem(parent); if (!parentItem) return {}; if (auto *childItem = parentItem->child(row)) return createIndex(row, column, childItem); return {}; }
Da jedes Element Informationen für eine ganze Datenzeile enthält, erstellen wir einen Modellindex zur eindeutigen Identifizierung, indem wir die Funktion createIndex() mit den Zeilen- und Spaltennummern und einem Zeiger auf das Element aufrufen. In der Funktion data() verwenden wir den Zeiger auf das Element und die Spaltennummer, um auf die mit dem Modellindex verknüpften Daten zuzugreifen; in diesem Modell wird die Zeilennummer nicht zur Identifizierung der Daten benötigt.
Die Funktion parent()
liefert Modellindizes für übergeordnete Elemente, indem sie das entsprechende Element für einen gegebenen Modellindex findet, seine Funktion parent() verwendet, um das übergeordnete Element zu erhalten, und dann einen Modellindex erstellt, der das übergeordnete Element darstellt. (Siehe das obige Diagramm).
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{}; }
Elemente ohne Eltern, einschließlich des Stammelements, werden behandelt, indem ein Null-Modellindex zurückgegeben wird. Andernfalls wird ein Modellindex erstellt und wie in der index() -Funktion zurückgegeben, mit einer geeigneten Zeilennummer, aber mit einer Null-Spaltennummer, um mit dem in der index()-Implementierung verwendeten Schema konsistent zu sein.
Testen des Modells
Die korrekte Implementierung eines Elementmodells kann eine Herausforderung sein. Die Klasse QAbstractItemModelTester aus dem Qt Test Modul prüft die Konsistenz des Modells, z. B. die Erstellung des Modellindex und die Eltern-Kind-Beziehungen.
Sie können Ihr Modell testen, indem Sie einfach eine Modellinstanz an den Klassenkonstruktor übergeben, z. B. als Teil eines Qt-Unit-Tests:
class TestEditableTreeModel : public QObject { Q_OBJECTprivate slots: void testTreeModel(); };void TestEditableTreeModel::testTreeModel() { constexpr auto fileName = ":/default.txt"_L1; QFile file(Dateiname); 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"
Um einen Test zu erstellen, der mit der ausführbaren Datei ctest
ausgeführt werden kann, wird add_test()
verwendet:
# 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)
© 2025 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.