From 157605bbcfcfc00dcb125eecc061f46399a23246 Mon Sep 17 00:00:00 2001 From: Phlosioneer Date: Tue, 7 Jan 2020 11:02:08 -0500 Subject: [PATCH] Give scripts access to existing file formats (#2716) Scripts can now call pre-defined map/tileset formats, and can query what map/tileset formats are available. Closes #2696 --- docs/reference/scripting.rst | 95 +++++++++++++++++ src/tiled/scriptfileformatwrappers.cpp | 135 +++++++++++++++++++++++++ src/tiled/scriptfileformatwrappers.h | 80 +++++++++++++++ src/tiled/scriptmanager.cpp | 3 + src/tiled/scriptmodule.cpp | 69 +++++++++++++ src/tiled/scriptmodule.h | 11 ++ src/tiled/tiled.pro | 2 + src/tiled/tiled.qbs | 2 + 8 files changed, 397 insertions(+) create mode 100644 src/tiled/scriptfileformatwrappers.cpp create mode 100644 src/tiled/scriptfileformatwrappers.h diff --git a/docs/reference/scripting.rst b/docs/reference/scripting.rst index e1fd4ceb6c..62b6f31081 100644 --- a/docs/reference/scripting.rst +++ b/docs/reference/scripting.rst @@ -129,6 +129,12 @@ Properties **openAssets** : array |ro|, "List of currently opened :ref:`assets `." **mapEditor** : :ref:`script-mapeditor`, "Access the editor used when editing maps." **tilesetEditor** : :ref:`script-tileseteditor`, "Access the editor used when editing tilesets." + **tilesetFormats** : [string] |ro|, "List of supported tileset format names. Use + :ref:`tilesetFormat ` to get the corresponding format object + to read and write files. (Since 1.4)" + **mapFormats** : [string] |ro|, "List of supported map format names. Use + :ref:`mapFormat ` to get the corresponding format object to + read and write files. (Since 1.4)" Functions ~~~~~~~~~ @@ -432,6 +438,31 @@ tiled.extendMenu(id : string, items : array | object) : void The "CustomAction" will need to have been registered before using :ref:`tiled.registerAction() `. + +.. _script-tilesetFormat: + +tiled.tilesetFormat(shortName : string) : :ref:`script-tilesetformatwrapper` + Returns the tileset format object with the given name, or `undefined` if + no object was found. See the `tilesetFormats` property for more info. + +.. _script-tilesetFormatForFile: + +tiled.tilesetFormatForFile(fileName : string) : :ref:`script-tilesetformatwrapper` + Returns the tileset format object that can read the given file, or `undefined` + if no object was found. + +.. _script-mapFormat: + +tiled.mapFormat(shortName : string) : :ref:`script-mapformatwrapper` + Returns the map format object with the given name, or `undefined` if no object + was found. See the `mapFormats` property for more info. + +.. _script-mapFormatForFile: + +tiled.mapFormatForFile(fileName : string) : :ref:`script-mapformatwrapper` + Returns the map format object that can read the given file, or `undefined` if + no object was found. + .. _script-tiled-signals: Signals @@ -537,6 +568,28 @@ Asset.macro(text : string, callback : function) : value The returned value is whatever the callback function returned. +.. _script-fileformat: + +File Format +^^^^^^^^^^^ + +Common functionality for file format readers and writers. (Since 1.4) + +Properties +~~~~~~~~~~ + +.. csv-table:: + widths: 1, 2 + + **canRead** : bool |ro|, Whether this format supports reading files. + **canWrite** : bool |ro|, Whether this format supports writing files. + +Functions +~~~~~~~~~ + +FileFormat.supportsFile(fileName : string) : bool + Returns whether the file is readable by this format. + .. _script-grouplayer: GroupLayer @@ -690,6 +743,27 @@ Properties **currentMapView** : :ref:`script-mapview` |ro|, "Access the current map view." **tilesetsView** : :ref:`script-tilesetsview` |ro|, "Access the Tilesets view." +.. _script-mapformatwrapper: + +Map Format +^^^^^^^^^^ + +This is an object that can read or write map files. (Since 1.4) + +Inherits :ref:`script-fileformat`. + +Functions +~~~~~~~~~ + +MapFormat.read(fileName : string) : :ref:`script-map` + Read the given file as a map. This function will throw an error if it + is not supported. + +MapFormat.write(map : :ref:`script-map`, fileName : string) : string + Write the given map to a file. This function will throw an error if it + is not supported. If there is an error writing the file, it will return a + description of the error; otherwise, it will return "". + .. _script-mapview: Map View @@ -1296,6 +1370,27 @@ Properties **collisionEditor** : :ref:`script-tilecollisioneditor`, "Access the collision editor within the tileset editor." +.. _script-tilesetformatwrapper: + +Tileset Format +^^^^^^^^^^^^^^ + +This is an object that can read or write tileset files. (Since 1.4) + +Inherits :ref:`script-fileformat`. + +Functions +~~~~~~~~~ + +TilesetFormat.read(fileName : string) : :ref:`script-tileset` + Read the given file as a tileset. This function will throw an error if it + is not supported. + +TilesetFormat.write(tileset : :ref:`script-tileset`, fileName : string) : string + Write the given tileset to a file. This function will throw an error if it + is not supported. If there is an error writing the file, it will return a + description of the error; otherwise, it will return "". + .. _script-tilesetsview: Tilesets View diff --git a/src/tiled/scriptfileformatwrappers.cpp b/src/tiled/scriptfileformatwrappers.cpp new file mode 100644 index 0000000000..84fd05bf3a --- /dev/null +++ b/src/tiled/scriptfileformatwrappers.cpp @@ -0,0 +1,135 @@ +/* + * scriptfileformatwrappers.h + * Copyright 2019, Phlosioneer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "scriptfileformatwrappers.h" + +#include "editablemap.h" +#include "editabletileset.h" +#include "mapformat.h" +#include "scriptedfileformat.h" +#include "scriptmanager.h" +#include "tilesetformat.h" + +#include +#include + +namespace Tiled { + +ScriptFileFormatWrapper::ScriptFileFormatWrapper(FileFormat *format, QObject *parent) + : QObject(parent), + mFormat(format) +{} + +bool ScriptFileFormatWrapper::supportsFile(const QString &filename) const +{ + return mFormat->supportsFile(filename); +} + +bool ScriptFileFormatWrapper::canRead() const +{ + return mFormat->capabilities() & FileFormat::Read; +} + +bool ScriptFileFormatWrapper::canWrite() const +{ + return mFormat->capabilities() & FileFormat::Write; +} + +bool ScriptFileFormatWrapper::assertCanRead() const +{ + if (canRead()) + return true; + auto message = QCoreApplication::translate("Script Errors", "File format doesn't support `read`"); + ScriptManager::instance().throwError(message); + return false; +} + +bool ScriptFileFormatWrapper::assertCanWrite() const +{ + if (canWrite()) + return true; + auto message = QCoreApplication::translate("Script Errors", "File format doesn't support `write`"); + ScriptManager::instance().throwError(message); + return false; +} + + +ScriptTilesetFormatWrapper::ScriptTilesetFormatWrapper(TilesetFormat* format, QObject *parent) + : ScriptFileFormatWrapper(format, parent) +{} + +EditableTileset *ScriptTilesetFormatWrapper::read(const QString &filename) +{ + if (!assertCanRead()) + return nullptr; + + auto tileset = static_cast(mFormat)->read(filename); + if (!tileset) { + auto message = QCoreApplication::translate("Script Errors", "Error reading tileset"); + ScriptManager::instance().throwError(message); + return nullptr; + } + + return new EditableTileset(tileset.data()); +} + +void ScriptTilesetFormatWrapper::write(EditableTileset *editable, const QString &filename) +{ + if (!assertCanWrite()) + return; + + auto tileset = editable->tileset(); + auto success = static_cast(mFormat)->write(*tileset, filename); + if (!success) + ScriptManager::instance().throwError(mFormat->errorString()); +} + + +ScriptMapFormatWrapper::ScriptMapFormatWrapper(MapFormat *format, QObject *parent) + : ScriptFileFormatWrapper(format, parent) +{} + +EditableMap *ScriptMapFormatWrapper::read(const QString &filename) +{ + if (!assertCanRead()) + return nullptr; + + auto map = static_cast(mFormat)->read(filename); + if (!map) { + auto message = QCoreApplication::translate("Script Errors", "Error reading map"); + ScriptManager::instance().throwError(message); + return nullptr; + } + + return new EditableMap(std::move(map)); +} + +void ScriptMapFormatWrapper::write(EditableMap *editable, const QString &filename) +{ + if (!assertCanWrite()) + return; + + auto map = editable->map(); + auto success = static_cast(mFormat)->write(map, filename); + if (!success) + ScriptManager::instance().throwError(mFormat->errorString()); +} + +} // namespace Tiled diff --git a/src/tiled/scriptfileformatwrappers.h b/src/tiled/scriptfileformatwrappers.h new file mode 100644 index 0000000000..ee43a9df69 --- /dev/null +++ b/src/tiled/scriptfileformatwrappers.h @@ -0,0 +1,80 @@ +/* + * scriptfileformatwrappers.h + * Copyright 2019, Phlosioneer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include + +namespace Tiled { + +class EditableTileset; +class EditableMap; +class MapFormat; +class TilesetFormat; +class FileFormat; + +class ScriptFileFormatWrapper : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool canRead READ canRead) + Q_PROPERTY(bool canWrite READ canWrite) + +public: + explicit ScriptFileFormatWrapper(FileFormat *format, QObject *parent = nullptr); + + Q_INVOKABLE bool supportsFile(const QString &filename) const; + + bool canRead() const; + bool canWrite() const; + +protected: + bool assertCanRead() const; + bool assertCanWrite() const; + + FileFormat *mFormat; +}; + +class ScriptTilesetFormatWrapper : public ScriptFileFormatWrapper +{ + Q_OBJECT + +public: + explicit ScriptTilesetFormatWrapper(TilesetFormat *format, QObject *parent = nullptr); + + Q_INVOKABLE Tiled::EditableTileset *read(const QString &filename); + Q_INVOKABLE void write(Tiled::EditableTileset *tileset, const QString &filename); +}; + +class ScriptMapFormatWrapper : public ScriptFileFormatWrapper +{ + Q_OBJECT + +public: + explicit ScriptMapFormatWrapper(MapFormat *format, QObject *parent = nullptr); + + Q_INVOKABLE Tiled::EditableMap *read(const QString &filename); + Q_INVOKABLE void write(Tiled::EditableMap *map, const QString &filename); +}; + +} // namespace Tiled + +Q_DECLARE_METATYPE(Tiled::ScriptTilesetFormatWrapper*) +Q_DECLARE_METATYPE(Tiled::ScriptMapFormatWrapper*) diff --git a/src/tiled/scriptmanager.cpp b/src/tiled/scriptmanager.cpp index 5c32978e03..5da857c61a 100644 --- a/src/tiled/scriptmanager.cpp +++ b/src/tiled/scriptmanager.cpp @@ -37,6 +37,7 @@ #include "scriptedfileformat.h" #include "scriptedtool.h" #include "scriptfile.h" +#include "scriptfileformatwrappers.h" #include "scriptmodule.h" #include "tilecollisiondock.h" #include "tilelayer.h" @@ -104,6 +105,8 @@ ScriptManager::ScriptManager(QObject *parent) qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); connect(&mWatcher, &FileSystemWatcher::filesChanged, this, &ScriptManager::scriptFilesChanged); diff --git a/src/tiled/scriptmodule.cpp b/src/tiled/scriptmodule.cpp index b544d54a69..92030406e2 100644 --- a/src/tiled/scriptmodule.cpp +++ b/src/tiled/scriptmodule.cpp @@ -31,6 +31,7 @@ #include "scriptedaction.h" #include "scriptedfileformat.h" #include "scriptedtool.h" +#include "scriptfileformatwrappers.h" #include "scriptmanager.h" #include "tilesetdocument.h" #include "tileseteditor.h" @@ -40,6 +41,7 @@ #include #include #include +#include namespace Tiled { @@ -113,6 +115,28 @@ QStringList ScriptModule::menus() const return idsToNames(ActionManager::menus()); } +QStringList ScriptModule::mapFormats() const +{ + const auto formats = PluginManager::objects(); + QStringList ret; + ret.reserve(formats.length()); + for (auto format : formats) + ret.append(format->shortName()); + + return ret; +} + +QStringList ScriptModule::tilesetFormats() const +{ + const auto formats = PluginManager::objects(); + QStringList ret; + ret.reserve(formats.length()); + for (auto format : formats) + ret.append(format->shortName()); + + return ret; +} + EditableAsset *ScriptModule::activeAsset() const { auto documentManager = DocumentManager::instance(); @@ -282,6 +306,51 @@ QJSValue ScriptModule::registerTool(const QString &shortName, QJSValue toolObjec return toolObject; } +ScriptMapFormatWrapper *ScriptModule::mapFormat(const QString &shortName) const +{ + const auto formats = PluginManager::objects(); + for (auto format : formats) { + if (format->shortName() == shortName) + return new ScriptMapFormatWrapper(format); + } + + return nullptr; +} + +ScriptMapFormatWrapper *ScriptModule::mapFormatForFile(const QString &fileName) const +{ + const auto formats = PluginManager::objects(); + for (auto format : formats) { + if (format->supportsFile(fileName)) + return new ScriptMapFormatWrapper(format); + } + + return nullptr; +} + +ScriptTilesetFormatWrapper *ScriptModule::tilesetFormat(const QString &shortName) const +{ + const auto formats = PluginManager::objects(); + for (auto format : formats) { + if (format->shortName() == shortName) + return new ScriptTilesetFormatWrapper(format); + } + + return nullptr; +} + +ScriptTilesetFormatWrapper *ScriptModule::tilesetFormatForFile(const QString &fileName) const +{ + const auto formats = PluginManager::objects(); + for (auto format : formats) { + if (format->supportsFile(fileName)) + return new ScriptTilesetFormatWrapper(format); + } + + return nullptr; +} + + static QString toString(QJSValue value) { if (value.isString()) diff --git a/src/tiled/scriptmodule.h b/src/tiled/scriptmodule.h index 97976928c3..406ef43360 100644 --- a/src/tiled/scriptmodule.h +++ b/src/tiled/scriptmodule.h @@ -40,6 +40,8 @@ class MapEditor; class ScriptedAction; class ScriptedMapFormat; class ScriptedTilesetFormat; +class ScriptMapFormatWrapper; +class ScriptTilesetFormatWrapper; class ScriptedTool; class TilesetEditor; @@ -56,6 +58,8 @@ class ScriptModule : public QObject Q_PROPERTY(QStringList actions READ actions) Q_PROPERTY(QStringList menus READ menus) + Q_PROPERTY(QStringList mapFormats READ mapFormats) + Q_PROPERTY(QStringList tilesetFormats READ tilesetFormats) Q_PROPERTY(Tiled::EditableAsset *activeAsset READ activeAsset WRITE setActiveAsset NOTIFY activeAssetChanged) Q_PROPERTY(QList openAssets READ openAssets) @@ -84,6 +88,8 @@ class ScriptModule : public QObject QStringList actions() const; QStringList menus() const; + QStringList mapFormats() const; + QStringList tilesetFormats() const; EditableAsset *activeAsset() const; bool setActiveAsset(EditableAsset *asset) const; @@ -102,6 +108,11 @@ class ScriptModule : public QObject Q_INVOKABLE void registerTilesetFormat(const QString &shortName, QJSValue tilesetFormatObject); Q_INVOKABLE QJSValue registerTool(const QString &shortName, QJSValue toolObject); + Q_INVOKABLE Tiled::ScriptMapFormatWrapper *mapFormat(const QString &shortName) const; + Q_INVOKABLE Tiled::ScriptMapFormatWrapper *mapFormatForFile(const QString &fileName) const; + Q_INVOKABLE Tiled::ScriptTilesetFormatWrapper *tilesetFormat(const QString &shortName) const; + Q_INVOKABLE Tiled::ScriptTilesetFormatWrapper *tilesetFormatForFile(const QString &fileName) const; + Q_INVOKABLE void extendMenu(const QByteArray &idName, QJSValue items); signals: diff --git a/src/tiled/tiled.pro b/src/tiled/tiled.pro index b37d5bb357..132834c5d5 100644 --- a/src/tiled/tiled.pro +++ b/src/tiled/tiled.pro @@ -207,6 +207,7 @@ SOURCES += aboutdialog.cpp \ scriptedfileformat.cpp \ scriptedtool.cpp \ scriptfile.cpp \ + scriptfileformatwrappers.cpp \ scriptmanager.cpp \ scriptmodule.cpp \ selectionrectangle.cpp \ @@ -438,6 +439,7 @@ HEADERS += aboutdialog.h \ scriptedfileformat.h \ scriptedtool.h \ scriptfile.h \ + scriptfileformatwrappers.h \ scriptmanager.h \ scriptmodule.h \ selectionrectangle.h \ diff --git a/src/tiled/tiled.qbs b/src/tiled/tiled.qbs index 03d7c11aa1..3c2772e11d 100644 --- a/src/tiled/tiled.qbs +++ b/src/tiled/tiled.qbs @@ -414,6 +414,8 @@ QtGuiApplication { "scriptedtool.h", "scriptfile.cpp", "scriptfile.h", + "scriptfileformatwrappers.cpp", + "scriptfileformatwrappers.h", "scriptmanager.cpp", "scriptmanager.h", "scriptmodule.cpp",