overte-JulianGro/tools/animedit/treemodel.cpp
2019-11-22 15:40:44 -08:00

418 lines
12 KiB
C++

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