//// TreeModel//// Created by Anthony Thibault on 6/5/2019// Copyright 2019 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "treeitem.h" #include <QDebug>#include <QFile>#include <QtGlobal>#include <QJsonDocument>#include <QJsonObject>#include <QJsonArray>#include <QStringList>#include <QJSValue> #include "treemodel.h" static TreeItem* newBlankTreeItem() { QList<QVariant> columnData; columnData << "newNode"; columnData << "clip"; columnData << QJsonObject(); // blank return new TreeItem(columnData);} TreeModel::TreeModel(QObject* parent) : QAbstractItemModel(parent) { _roleNameMapping[TreeModelRoleName] = "name"; _roleNameMapping[TreeModelRoleType] = "type"; _roleNameMapping[TreeModelRoleData] = "data"; QList<QVariant> rootData; rootData << "Name" << "Type" << "Data"; _rootItem = new TreeItem(rootData); _clipboard = nullptr;} TreeModel::~TreeModel() { delete _rootItem;} QHash<int, QByteArray> TreeModel::roleNames() const { return _roleNameMapping;} Qt::ItemFlags TreeModel::flags(const QModelIndex& index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return QAbstractItemModel::flags(index);} QVariant TreeModel::data(const QModelIndex& index, int role) const { TreeItem* item = getItem(index); return item->data(role - Qt::UserRole - 1);} QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { return _rootItem->data(section); } return QVariant();} QModelIndex TreeModel::index(int row, int column, const QModelIndex& parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } TreeItem *parentItem; if (!parent.isValid()) { parentItem = _rootItem; } else { parentItem = static_cast<TreeItem*>(parent.internalPointer()); } TreeItem *childItem = parentItem->child(row); if (childItem) { return createIndex(row, column, childItem); } else { return QModelIndex(); }} QModelIndex TreeModel::parent(const QModelIndex& index) const { if (!index.isValid()) { return QModelIndex(); } TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer()); TreeItem *parentItem = childItem->parentItem(); if (parentItem == _rootItem) { return QModelIndex(); } return createIndex(parentItem->row(), 0, parentItem);} int TreeModel::rowCount(const QModelIndex& parent) const { TreeItem* parentItem = getItem(parent); return parentItem->childCount();} int TreeModel::columnCount(const QModelIndex& parent) const { return _rootItem->columnCount();} bool TreeModel::setData(const QModelIndex& index, const QVariant& value, int role) { TreeItem* item = getItem(index); bool returnValue = item->setData(role - Qt::UserRole - 1, value); emit dataChanged(index, index); return returnValue;} bool TreeModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) { return false;} bool TreeModel::insertColumns(int position, int columns, const QModelIndex& parent) { return false;} bool TreeModel::removeColumns(int position, int columns, const QModelIndex& parent) { return false;} bool TreeModel::insertRows(int position, int rows, const QModelIndex& parent) { return false;} bool TreeModel::removeRows(int position, int rows, const QModelIndex& parent) { return false;} void TreeModel::loadFromFile(const QString& filename) { beginResetModel(); QFile file(filename); if (!file.exists()) { qCritical() << "TreeModel::loadFromFile, failed to open file" << filename; } else if (!file.open(QIODevice::ReadOnly)) { qCritical() << "TreeModel::loadFromFile, failed to open file" << filename; } else { qDebug() << "TreeModel::loadFromFile, success opening file" << filename; QByteArray contents = file.readAll(); QJsonParseError error; auto doc = QJsonDocument::fromJson(contents, &error); if (error.error != QJsonParseError::NoError) { qCritical() << "TreeModel::loadFromFile, failed to parse json, error" << error.errorString(); } else { QJsonObject obj = doc.object(); // version QJsonValue versionVal = obj.value("version"); if (!versionVal.isString()) { qCritical() << "TreeModel::loadFromFile, bad string \"version\""; return; } QString version = versionVal.toString(); // check version if (version != "1.0" && version != "1.1") { qCritical() << "TreeModel::loadFromFile, bad version number" << version << "expected \"1.0\" or \"1.1\""; return; } // root QJsonValue rootVal = obj.value("root"); if (!rootVal.isObject()) { qCritical() << "TreeModel::loadFromFile, bad object \"root\""; return; } QList<QVariant> columnData; columnData << QString("root"); columnData << QString("root"); columnData << QString("root"); // create root item _rootItem = new TreeItem(columnData); _rootItem->appendChild(loadNode(rootVal.toObject())); } } endResetModel();} void TreeModel::saveToFile(const QString& filename) { QJsonObject obj; obj.insert("version", "1.1"); const int FIRST_CHILD = 0; obj.insert("root", jsonFromItem(_rootItem->child(FIRST_CHILD))); QJsonDocument doc(obj); QByteArray byteArray = doc.toJson(QJsonDocument::Indented); QFile file(filename); if (!file.open(QIODevice::WriteOnly)) { qCritical() << "TreeModel::safeToFile, failed to open file" << filename; } else { file.write(byteArray); }} void TreeModel::newNode(const QModelIndex& parent) { TreeItem* parentItem = _rootItem; if (parent.isValid()) { parentItem = static_cast<TreeItem*>(parent.internalPointer()); } beginInsertRows(parent, parentItem->childCount(), parentItem->childCount()); TreeItem* childItem = newBlankTreeItem(); parentItem->appendChild(childItem); endInsertRows();} void TreeModel::deleteNode(const QModelIndex& index) { TreeItem* item = static_cast<TreeItem*>(index.internalPointer()); TreeItem* parentItem = item->parentItem(); int childNum = parentItem->findChild(item); if (childNum >= 0) { beginRemoveRows(createIndex(0, 0, reinterpret_cast<quintptr>(parentItem)), childNum, childNum); parentItem->removeChild(childNum); endRemoveRows(); }} void TreeModel::insertNodeAbove(const QModelIndex& index) { TreeItem* item = static_cast<TreeItem*>(index.internalPointer()); TreeItem* parentItem = item->parentItem(); int childNum = parentItem->findChild(item); if (childNum >= 0) { TreeItem* newItem = newBlankTreeItem(); QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast<quintptr>(parentItem)); // remove item beginRemoveRows(parentIndex, childNum, childNum); parentItem->removeChild(childNum); endRemoveRows(); // append item to newItem newItem->appendChild(item); // then insert newItem beginInsertRows(parentIndex, childNum, childNum); parentItem->insertChild(childNum, newItem); endInsertRows(); }} QVariantList TreeModel::getChildrenModelIndices(const QModelIndex& index) { QVariantList indices; TreeItem* parent = static_cast<TreeItem*>(index.internalPointer()); for (int i = 0; i < parent->childCount(); ++i) { TreeItem* child = parent->child(i); indices.push_back(createIndex(i, 0, reinterpret_cast<quintptr>(child))); } return indices;} void TreeModel::copyNode(const QModelIndex& index) { TreeItem* item = static_cast<TreeItem*>(index.internalPointer()); // TODO: delete previous clipboard _clipboard = item->cloneNode();} void TreeModel::copyNodeAndChildren(const QModelIndex& index) { TreeItem* item = static_cast<TreeItem*>(index.internalPointer()); // TODO: delete previous clipboard _clipboard = item->cloneNodeAndChildren();} void TreeModel::pasteOver(const QModelIndex& index) { if (_clipboard) { TreeItem* item = static_cast<TreeItem*>(index.internalPointer()); TreeItem* parentItem = item->parentItem(); int childNum = parentItem->findChild(item); if (childNum >= 0) { QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast<quintptr>(parentItem)); // remove item beginRemoveRows(parentIndex, childNum, childNum); parentItem->removeChild(childNum); endRemoveRows(); // then insert clone of _clipboard beginInsertRows(parentIndex, childNum, childNum); parentItem->insertChild(childNum, _clipboard->cloneNodeAndChildren()); endInsertRows(); } }} void TreeModel::pasteAsChild(const QModelIndex& index) { if (_clipboard) { TreeItem* parentItem = _rootItem; if (index.isValid()) { parentItem = static_cast<TreeItem*>(index.internalPointer()); } beginInsertRows(index, parentItem->childCount(), parentItem->childCount()); parentItem->appendChild(_clipboard->cloneNodeAndChildren()); endInsertRows(); }} TreeItem* TreeModel::loadNode(const QJsonObject& jsonObj) { // id auto idVal = jsonObj.value("id"); if (!idVal.isString()) { qCritical() << "loadNode, bad string \"id\""; return nullptr; } QString id = idVal.toString(); // type auto typeVal = jsonObj.value("type"); if (!typeVal.isString()) { qCritical() << "loadNode, bad object \"type\", id =" << id; return nullptr; } QString typeStr = typeVal.toString(); // data auto dataValue = jsonObj.value("data"); if (!dataValue.isObject()) { qCritical() << "AnimNodeLoader, bad string \"data\", id =" << id; return nullptr; } QList<QVariant> columnData; columnData << id; columnData << typeStr; columnData << dataValue.toVariant(); // create node TreeItem* node = new TreeItem(columnData); // children auto childrenValue = jsonObj.value("children"); if (!childrenValue.isArray()) { qCritical() << "AnimNodeLoader, bad array \"children\", id =" << id; return nullptr; } auto childrenArray = childrenValue.toArray(); for (const auto& childValue : childrenArray) { if (!childValue.isObject()) { qCritical() << "AnimNodeLoader, bad object in \"children\", id =" << id; return nullptr; } TreeItem* child = loadNode(childValue.toObject()); if (child) { node->appendChild(child); } else { return nullptr; } } return node;} TreeItem* TreeModel::getItem(const QModelIndex& index) const { if (index.isValid()) { TreeItem *item = static_cast<TreeItem*>(index.internalPointer()); if (item) { return item; } } return _rootItem;} QJsonObject TreeModel::jsonFromItem(TreeItem* treeItem) { QJsonObject obj; const int ID_COLUMN = 0; obj.insert("id", treeItem->data(ID_COLUMN).toJsonValue()); const int TYPE_COLUMN = 1; obj.insert("type", treeItem->data(TYPE_COLUMN).toJsonValue()); const int DATA_COLUMN = 2; QVariant data = treeItem->data(DATA_COLUMN); if (data.canConvert<QJSValue>()) { obj.insert("data", data.value<QJSValue>().toVariant().toJsonValue()); } else { obj.insert("data", data.toJsonValue()); } QJsonArray children; for (int i = 0; i < treeItem->childCount(); i++) { children.push_back(jsonFromItem(treeItem->child(i))); } obj.insert("children", children); return obj;}
//
// TreeModel
//
// Created by Anthony Thibault on 6/5/2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "treeitem.h"
#include <QDebug>
#include <QFile>
#include <QtGlobal>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QStringList>
#include <QJSValue>
#include "treemodel.h"
static TreeItem* newBlankTreeItem() {
QList<QVariant> columnData;
columnData << "newNode";
columnData << "clip";
columnData << QJsonObject(); // blank
return new TreeItem(columnData);
}
TreeModel::TreeModel(QObject* parent) : QAbstractItemModel(parent) {
_roleNameMapping[TreeModelRoleName] = "name";
_roleNameMapping[TreeModelRoleType] = "type";
_roleNameMapping[TreeModelRoleData] = "data";
QList<QVariant> rootData;
rootData << "Name" << "Type" << "Data";
_rootItem = new TreeItem(rootData);
_clipboard = nullptr;
}
TreeModel::~TreeModel() {
delete _rootItem;
}
QHash<int, QByteArray> TreeModel::roleNames() const {
return _roleNameMapping;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex& index) const {
if (!index.isValid()) {
return Qt::NoItemFlags;
}
return QAbstractItemModel::flags(index);
}
QVariant TreeModel::data(const QModelIndex& index, int role) const {
TreeItem* item = getItem(index);
return item->data(role - Qt::UserRole - 1);
}
QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
return _rootItem->data(section);
}
return QVariant();
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex& parent) const {
if (!hasIndex(row, column, parent)) {
return QModelIndex();
}
TreeItem *parentItem;
if (!parent.isValid()) {
parentItem = _rootItem;
} else {
parentItem = static_cast<TreeItem*>(parent.internalPointer());
}
TreeItem *childItem = parentItem->child(row);
if (childItem) {
return createIndex(row, column, childItem);
} else {
return QModelIndex();
}
}
QModelIndex TreeModel::parent(const QModelIndex& index) const {
if (!index.isValid()) {
return QModelIndex();
}
TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer());
TreeItem *parentItem = childItem->parentItem();
if (parentItem == _rootItem) {
return QModelIndex();
}
return createIndex(parentItem->row(), 0, parentItem);
}
int TreeModel::rowCount(const QModelIndex& parent) const {
TreeItem* parentItem = getItem(parent);
return parentItem->childCount();
}
int TreeModel::columnCount(const QModelIndex& parent) const {
return _rootItem->columnCount();
}
bool TreeModel::setData(const QModelIndex& index, const QVariant& value, int role) {
TreeItem* item = getItem(index);
bool returnValue = item->setData(role - Qt::UserRole - 1, value);
emit dataChanged(index, index);
return returnValue;
}
bool TreeModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) {
return false;
}
bool TreeModel::insertColumns(int position, int columns, const QModelIndex& parent) {
return false;
}
bool TreeModel::removeColumns(int position, int columns, const QModelIndex& parent) {
return false;
}
bool TreeModel::insertRows(int position, int rows, const QModelIndex& parent) {
return false;
}
bool TreeModel::removeRows(int position, int rows, const QModelIndex& parent) {
return false;
}
void TreeModel::loadFromFile(const QString& filename) {
beginResetModel();
QFile file(filename);
if (!file.exists()) {
qCritical() << "TreeModel::loadFromFile, failed to open file" << filename;
} else if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "TreeModel::loadFromFile, failed to open file" << filename;
} else {
qDebug() << "TreeModel::loadFromFile, success opening file" << filename;
QByteArray contents = file.readAll();
QJsonParseError error;
auto doc = QJsonDocument::fromJson(contents, &error);
if (error.error != QJsonParseError::NoError) {
qCritical() << "TreeModel::loadFromFile, failed to parse json, error" << error.errorString();
} else {
QJsonObject obj = doc.object();
// version
QJsonValue versionVal = obj.value("version");
if (!versionVal.isString()) {
qCritical() << "TreeModel::loadFromFile, bad string \"version\"";
return;
}
QString version = versionVal.toString();
// check version
if (version != "1.0" && version != "1.1") {
qCritical() << "TreeModel::loadFromFile, bad version number" << version << "expected \"1.0\" or \"1.1\"";
return;
}
// root
QJsonValue rootVal = obj.value("root");
if (!rootVal.isObject()) {
qCritical() << "TreeModel::loadFromFile, bad object \"root\"";
return;
}
QList<QVariant> columnData;
columnData << QString("root");
columnData << QString("root");
columnData << QString("root");
// create root item
_rootItem = new TreeItem(columnData);
_rootItem->appendChild(loadNode(rootVal.toObject()));
}
}
endResetModel();
}
void TreeModel::saveToFile(const QString& filename) {
QJsonObject obj;
obj.insert("version", "1.1");
const int FIRST_CHILD = 0;
obj.insert("root", jsonFromItem(_rootItem->child(FIRST_CHILD)));
QJsonDocument doc(obj);
QByteArray byteArray = doc.toJson(QJsonDocument::Indented);
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
qCritical() << "TreeModel::safeToFile, failed to open file" << filename;
} else {
file.write(byteArray);
}
}
void TreeModel::newNode(const QModelIndex& parent) {
TreeItem* parentItem = _rootItem;
if (parent.isValid()) {
parentItem = static_cast<TreeItem*>(parent.internalPointer());
}
beginInsertRows(parent, parentItem->childCount(), parentItem->childCount());
TreeItem* childItem = newBlankTreeItem();
parentItem->appendChild(childItem);
endInsertRows();
}
void TreeModel::deleteNode(const QModelIndex& index) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
TreeItem* parentItem = item->parentItem();
int childNum = parentItem->findChild(item);
if (childNum >= 0) {
beginRemoveRows(createIndex(0, 0, reinterpret_cast<quintptr>(parentItem)), childNum, childNum);
parentItem->removeChild(childNum);
endRemoveRows();
}
}
void TreeModel::insertNodeAbove(const QModelIndex& index) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
TreeItem* parentItem = item->parentItem();
int childNum = parentItem->findChild(item);
if (childNum >= 0) {
TreeItem* newItem = newBlankTreeItem();
QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast<quintptr>(parentItem));
// remove item
beginRemoveRows(parentIndex, childNum, childNum);
parentItem->removeChild(childNum);
endRemoveRows();
// append item to newItem
newItem->appendChild(item);
// then insert newItem
beginInsertRows(parentIndex, childNum, childNum);
parentItem->insertChild(childNum, newItem);
endInsertRows();
}
}
QVariantList TreeModel::getChildrenModelIndices(const QModelIndex& index) {
QVariantList indices;
TreeItem* parent = static_cast<TreeItem*>(index.internalPointer());
for (int i = 0; i < parent->childCount(); ++i) {
TreeItem* child = parent->child(i);
indices.push_back(createIndex(i, 0, reinterpret_cast<quintptr>(child)));
}
return indices;
}
void TreeModel::copyNode(const QModelIndex& index) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
// TODO: delete previous clipboard
_clipboard = item->cloneNode();
}
void TreeModel::copyNodeAndChildren(const QModelIndex& index) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
// TODO: delete previous clipboard
_clipboard = item->cloneNodeAndChildren();
}
void TreeModel::pasteOver(const QModelIndex& index) {
if (_clipboard) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
TreeItem* parentItem = item->parentItem();
int childNum = parentItem->findChild(item);
if (childNum >= 0) {
QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast<quintptr>(parentItem));
// remove item
beginRemoveRows(parentIndex, childNum, childNum);
parentItem->removeChild(childNum);
endRemoveRows();
// then insert clone of _clipboard
beginInsertRows(parentIndex, childNum, childNum);
parentItem->insertChild(childNum, _clipboard->cloneNodeAndChildren());
endInsertRows();
}
}
}
void TreeModel::pasteAsChild(const QModelIndex& index) {
if (_clipboard) {
TreeItem* parentItem = _rootItem;
if (index.isValid()) {
parentItem = static_cast<TreeItem*>(index.internalPointer());
}
beginInsertRows(index, parentItem->childCount(), parentItem->childCount());
parentItem->appendChild(_clipboard->cloneNodeAndChildren());
endInsertRows();
}
}
TreeItem* TreeModel::loadNode(const QJsonObject& jsonObj) {
// id
auto idVal = jsonObj.value("id");
if (!idVal.isString()) {
qCritical() << "loadNode, bad string \"id\"";
return nullptr;
}
QString id = idVal.toString();
// type
auto typeVal = jsonObj.value("type");
if (!typeVal.isString()) {
qCritical() << "loadNode, bad object \"type\", id =" << id;
return nullptr;
}
QString typeStr = typeVal.toString();
// data
auto dataValue = jsonObj.value("data");
if (!dataValue.isObject()) {
qCritical() << "AnimNodeLoader, bad string \"data\", id =" << id;
return nullptr;
}
QList<QVariant> columnData;
columnData << id;
columnData << typeStr;
columnData << dataValue.toVariant();
// create node
TreeItem* node = new TreeItem(columnData);
// children
auto childrenValue = jsonObj.value("children");
if (!childrenValue.isArray()) {
qCritical() << "AnimNodeLoader, bad array \"children\", id =" << id;
return nullptr;
}
auto childrenArray = childrenValue.toArray();
for (const auto& childValue : childrenArray) {
if (!childValue.isObject()) {
qCritical() << "AnimNodeLoader, bad object in \"children\", id =" << id;
return nullptr;
}
TreeItem* child = loadNode(childValue.toObject());
if (child) {
node->appendChild(child);
} else {
return nullptr;
}
}
return node;
}
TreeItem* TreeModel::getItem(const QModelIndex& index) const {
if (index.isValid()) {
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
if (item) {
return item;
}
}
return _rootItem;
}
QJsonObject TreeModel::jsonFromItem(TreeItem* treeItem) {
QJsonObject obj;
const int ID_COLUMN = 0;
obj.insert("id", treeItem->data(ID_COLUMN).toJsonValue());
const int TYPE_COLUMN = 1;
obj.insert("type", treeItem->data(TYPE_COLUMN).toJsonValue());
const int DATA_COLUMN = 2;
QVariant data = treeItem->data(DATA_COLUMN);
if (data.canConvert<QJSValue>()) {
obj.insert("data", data.value<QJSValue>().toVariant().toJsonValue());
} else {
obj.insert("data", data.toJsonValue());
}
QJsonArray children;
for (int i = 0; i < treeItem->childCount(); i++) {
children.push_back(jsonFromItem(treeItem->child(i)));
}
obj.insert("children", children);
return obj;
}