Skip to content

Commit

Permalink
Added support for storing object type definitions in JSON format
Browse files Browse the repository at this point in the history
Issue #1313
  • Loading branch information
bjorn committed May 1, 2017
1 parent 021613f commit 0883d2c
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 91 deletions.
4 changes: 2 additions & 2 deletions docs/manual/custom-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ Types Editor, available from the _View_ menu.
By default, Tiled stores these object types in the user settings. However, since
you'll often want to share them with other people in your project, you can
export your object types or change the storage location of the object types
file. A simple XML file with self-explanatory contents is used to store your
object types.
file. A simple XML or JSON file with self-explanatory contents is used to
store your object types.

The color not only affects the rendering of the various shapes of objects, but
is also the color of the label which will show up if you give your object a
Expand Down
242 changes: 190 additions & 52 deletions src/tiled/objecttypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,112 @@
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>

namespace Tiled {
namespace Internal {

bool ObjectTypesWriter::writeObjectTypes(const QString &fileName,
const ObjectTypes &objectTypes)
static QString resolveReference(const QString &reference, const QString &filePath)
{
mError.clear();
if (!reference.isEmpty() && QDir::isRelativePath(reference))
return QDir::cleanPath(filePath + QLatin1Char('/') + reference);
return reference;
}

SaveFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
mError = QCoreApplication::translate(
"ObjectTypes", "Could not open file for writing.");
return false;
static QJsonObject toJson(const ObjectType &objectType, const QDir &fileDir)
{
const QString NAME = QStringLiteral("name");
const QString VALUE = QStringLiteral("value");
const QString TYPE = QStringLiteral("type");
const QString COLOR = QStringLiteral("color");
const QString PROPERTIES = QStringLiteral("properties");

QJsonObject json;
json.insert(NAME, objectType.name);

if (objectType.color.isValid())
json.insert(COLOR, objectType.color.name(QColor::HexArgb));

QJsonArray propertiesJson;

QMapIterator<QString,QVariant> it(objectType.defaultProperties);
while (it.hasNext()) {
it.next();

int type = it.value().userType();

const QString typeName = typeToName(it.value().userType());
QJsonValue value = QJsonValue::fromVariant(toExportValue(it.value()));

if (type == filePathTypeId())
value = fileDir.relativeFilePath(value.toString());

QJsonObject propertyJson;
propertyJson.insert(NAME, it.key());
propertyJson.insert(TYPE, typeName);
propertyJson.insert(VALUE, value);

propertiesJson.append(propertyJson);
}

const QDir fileDir(QFileInfo(fileName).path());
json.insert(PROPERTIES, propertiesJson);

return json;
}

static QJsonArray toJson(const ObjectTypes &objectTypes, const QDir &fileDir)
{
QJsonArray json;
for (const ObjectType &objectType : objectTypes)
json.append(toJson(objectType, fileDir));
return json;
}

static void fromJson(const QJsonObject &object, ObjectType &objectType, const QString &filePath)
{
objectType.name = object.value(QLatin1String("name")).toString();

const QString colorName = object.value(QLatin1String("color")).toString();
if (QColor::isValidColor(colorName))
objectType.color.setNamedColor(colorName);

QXmlStreamWriter writer(file.device());
const QJsonArray properties = object.value(QLatin1String("properties")).toArray();
for (const QJsonValue &property : properties) {
const QJsonObject propertyObject = property.toObject();
const QString name = propertyObject.value(QLatin1String("name")).toString();
const QString typeName = propertyObject.value(QLatin1String("type")).toString();
QVariant value = propertyObject.value(QLatin1String("value")).toVariant();

if (!typeName.isEmpty()) {
int type = nameToType(typeName);

if (type == filePathTypeId())
value = resolveReference(value.toString(), filePath);

value = fromExportValue(value, type);
}

objectType.defaultProperties.insert(name, value);
}
}

static void fromJson(const QJsonArray &array, ObjectTypes &objectTypes, const QString &filePath)
{
for (const QJsonValue &value : array) {
objectTypes.append(ObjectType());
fromJson(value.toObject(), objectTypes.last(), filePath);
}
}

static void writeObjectTypesXml(QFileDevice *device,
const QDir &fileDir,
const ObjectTypes &objectTypes)
{
QXmlStreamWriter writer(device);

writer.setAutoFormatting(true);
writer.setAutoFormattingIndent(1);
Expand Down Expand Up @@ -87,36 +172,44 @@ bool ObjectTypesWriter::writeObjectTypes(const QString &fileName,

writer.writeEndElement();
writer.writeEndDocument();

if (!file.commit()) {
mError = file.errorString();
return false;
}

return true;
}

ObjectTypes ObjectTypesReader::readObjectTypes(const QString &fileName)
static void readObjectTypePropertyXml(QXmlStreamReader &xml,
Properties &props,
const QString &filePath)
{
mError.clear();
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("property"));

ObjectTypes objectTypes;
const QXmlStreamAttributes atts = xml.attributes();
QString name(atts.value(QLatin1String("name")).toString());
QString typeName(atts.value(QLatin1String("type")).toString());
QVariant defaultValue(atts.value(QLatin1String("default")).toString());

QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
mError = QCoreApplication::translate(
"ObjectTypes", "Could not open file.");
return objectTypes;
if (!typeName.isEmpty()) {
int type = nameToType(typeName);

if (type == filePathTypeId())
defaultValue = resolveReference(defaultValue.toString(), filePath);

defaultValue = fromExportValue(defaultValue, type);
}

const QString filePath(QFileInfo(fileName).path());
props.insert(name, defaultValue);

QXmlStreamReader reader(&file);
xml.skipCurrentElement();
}

static void readObjectTypesXml(QFileDevice *device,
const QString &filePath,
ObjectTypes &objectTypes,
QString &error)
{
QXmlStreamReader reader(device);

if (!reader.readNextStartElement() || reader.name() != QLatin1String("objecttypes")) {
mError = QCoreApplication::translate(
error = QCoreApplication::translate(
"ObjectTypes", "File doesn't contain object types.");
return objectTypes;
return;
}

while (reader.readNextStartElement()) {
Expand All @@ -130,7 +223,7 @@ ObjectTypes ObjectTypesReader::readObjectTypes(const QString &fileName)
Properties props;
while (reader.readNextStartElement()) {
if (reader.name() == QLatin1String("property")){
readObjectTypeProperty(reader, props, filePath);
readObjectTypePropertyXml(reader, props, filePath);
} else {
reader.skipCurrentElement();
}
Expand All @@ -141,47 +234,92 @@ ObjectTypes ObjectTypesReader::readObjectTypes(const QString &fileName)
}

if (reader.hasError()) {
mError = QCoreApplication::translate("ObjectTypes",
error = QCoreApplication::translate("ObjectTypes",
"%3\n\nLine %1, column %2")
.arg(reader.lineNumber())
.arg(reader.columnNumber())
.arg(reader.errorString());
return objectTypes;
}
}

return objectTypes;
static ObjectTypesSerializer::Format detectFormat(const QString &fileName)
{
if (fileName.endsWith(QLatin1String(".json"), Qt::CaseInsensitive))
return ObjectTypesSerializer::Json;
else
return ObjectTypesSerializer::Xml;
}

static QString resolveReference(const QString &reference, const QString &filePath)

ObjectTypesSerializer::ObjectTypesSerializer(Format format)
: mFormat(format)
{
if (!reference.isEmpty() && QDir::isRelativePath(reference))
return QDir::cleanPath(filePath + QLatin1Char('/') + reference);
return reference;
}

void ObjectTypesReader::readObjectTypeProperty(QXmlStreamReader &xml,
Properties &props,
const QString &filePath)
bool ObjectTypesSerializer::writeObjectTypes(const QString &fileName,
const ObjectTypes &objectTypes)
{
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("property"));
mError.clear();

const QXmlStreamAttributes atts = xml.attributes();
QString name(atts.value(QLatin1String("name")).toString());
QString typeName(atts.value(QLatin1String("type")).toString());
QVariant defaultValue(atts.value(QLatin1String("default")).toString());
SaveFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
mError = QCoreApplication::translate(
"ObjectTypes", "Could not open file for writing.");
return false;
}

if (!typeName.isEmpty()) {
int type = nameToType(typeName);
const QDir fileDir(QFileInfo(fileName).path());

if (type == filePathTypeId())
defaultValue = resolveReference(defaultValue.toString(), filePath);
Format format = mFormat;
if (format == Autodetect)
format = detectFormat(fileName);

defaultValue = fromExportValue(defaultValue, type);
if (format == Xml) {
writeObjectTypesXml(file.device(), fileDir, objectTypes);
} else {
QJsonDocument document(toJson(objectTypes, fileDir));
file.device()->write(document.toJson());
}

props.insert(name, defaultValue);
if (!file.commit()) {
mError = file.errorString();
return false;
}

xml.skipCurrentElement();
return true;
}

bool ObjectTypesSerializer::readObjectTypes(const QString &fileName,
ObjectTypes &objectTypes)
{
mError.clear();

QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
mError = QCoreApplication::translate(
"ObjectTypes", "Could not open file.");
return false;
}

const QString filePath(QFileInfo(fileName).path());

Format format = mFormat;
if (format == Autodetect)
format = detectFormat(fileName);

if (format == Xml) {
readObjectTypesXml(&file, filePath, objectTypes, mError);
} else {
QJsonParseError jsonError;
const QByteArray json = file.readAll();
const QJsonDocument document = QJsonDocument::fromJson(json, &jsonError);
if (document.isNull())
mError = jsonError.errorString();
else
fromJson(document.array(), objectTypes, filePath);
}

return mError.isEmpty();
}

} // namespace Internal
Expand Down
29 changes: 12 additions & 17 deletions src/tiled/objecttypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
#include <QString>
#include <QColor>
#include <QVector>
#include <QXmlStreamReader>

#include "properties.h"

Expand Down Expand Up @@ -54,31 +53,27 @@ struct ObjectType
typedef QVector<ObjectType> ObjectTypes;


class ObjectTypesWriter
class ObjectTypesSerializer
{
public:
bool writeObjectTypes(const QString &fileName,
const ObjectTypes &objectTypes);

QString errorString() const { return mError; }

private:
QString mError;
};
enum Format {
Autodetect,
Xml,
Json
};

ObjectTypesSerializer(Format format = Autodetect);

class ObjectTypesReader
{
public:
ObjectTypes readObjectTypes(const QString &fileName);
bool writeObjectTypes(const QString &fileName,
const ObjectTypes &objectTypes);

void readObjectTypeProperty(QXmlStreamReader& xml,
Properties &props,
const QString &filePath);
bool readObjectTypes(const QString &fileName,
ObjectTypes &objectTypes);

QString errorString() const { return mError; }

private:
Format mFormat;
QString mError;
};

Expand Down
Loading

0 comments on commit 0883d2c

Please sign in to comment.