diff --git a/NEWS.md b/NEWS.md index ac3e609133..758caeb607 100644 --- a/NEWS.md +++ b/NEWS.md @@ -25,6 +25,7 @@ * Fixed alpha component of tint color not applying correctly to opaque images (by Roland Helmerichs, #4310) * Scripting: Added API for custom property types (with dogboydog, #3971) * Scripting: Added TileMap.chunkSize and TileMap.compressionLevel properties +* Scripting: Added tiled.session to read and write session properties (by Kanishka, #4345) * AutoMapping: Don't match rules based on empty input indexes * AutoMapping: Optimized reloading of rule maps and load rule maps on-demand * tBIN plugin: Added support for the tIDE XML format (by Casey Warrington, #4308) diff --git a/docs/scripting-doc/index.d.ts b/docs/scripting-doc/index.d.ts index 71d880d167..beda504b8b 100644 --- a/docs/scripting-doc/index.d.ts +++ b/docs/scripting-doc/index.d.ts @@ -415,7 +415,7 @@ declare namespace Qt { * Can be created with {@link tiled.cursor} and assigned to {@link * Tool.cursor}. */ - class QCursor {} + class QCursor { } /** * The base type from which all Qt widgets derive. @@ -753,13 +753,13 @@ declare namespace Qt { /** * A button which the user can push. */ - class QPushButton extends QAbstractButton {} + class QPushButton extends QAbstractButton { } /** * This type is returned in mainWidget when calling {@link Dialog.addSeparator}. * Qt documentation [QFrame](https://doc.qt.io/qt-6/qframe.html) */ - class QFrame extends QWidget {} + class QFrame extends QWidget { } /** * This type is returned when calling {@link QButtonGroup.addItem} or {@link QButtonGroup.addItems}. @@ -770,7 +770,7 @@ declare namespace Qt { * * @since 1.11.1 */ - class QRadioButton extends QAbstractButton {} + class QRadioButton extends QAbstractButton { } /** * A group of radio buttons where only one button can be selected. @@ -3963,7 +3963,7 @@ declare class WangSet extends TiledObject { * tiled.log("The color is black!"); * ``` */ -interface color {} +interface color { } /** * A container for tiles that can be used by a map. @@ -4632,6 +4632,96 @@ declare namespace Tiled { const Zstandard: CompressionMethod; } + +/** + * Provides access to session-specific settings. The session stores + * per-project preferences like last-used tile sizes, open files, and + * file states. + * + * Values are accessed by their string key (e.g. `"map.tileWidth"`). + * + * ### Known Session Properties + * + * | Key | Type | Default | Description | + * |-----|------|---------|-------------| + * | `"map.orientation"` | `number` (Map.Orientation) | `Map.Orthogonal` | Last used map orientation | + * | `"map.layerDataFormat"` | `number` (Map.LayerDataFormat) | `Map.CSV` | Last used layer data format | + * | `"map.renderOrder"` | `number` (Map.RenderOrder) | `Map.RightDown` | Last used render order | + * | `"map.fixedSize"` | `boolean` | `true` | Whether new maps use a fixed size | + * | `"map.width"` | `number` | `30` | Last used map width (in tiles) | + * | `"map.height"` | `number` | `20` | Last used map height (in tiles) | + * | `"map.tileWidth"` | `number` | `32` | Last used tile width (in pixels) | + * | `"map.tileHeight"` | `number` | `32` | Last used tile height (in pixels) | + * | `"map.lastUsedFormat"` | `string` | `""` | Last used map format | + * | `"map.lastUsedExportFilter"` | `string` | `""` | Last used map export filter | + * | `"tileset.type"` | `number` | `0` | Last used tileset type | + * | `"tileset.embedInMap"` | `boolean` | `false` | Whether to embed tileset in map | + * | `"tileset.useTransparentColor"` | `boolean` | `false` | Whether to use a transparent color | + * | `"tileset.transparentColor"` | `color` | magenta | Transparent color for tilesets | + * | `"tileset.tileSize"` | `size` | `{width: 32, height: 32}` | Last used tileset tile size | + * | `"tileset.spacing"` | `number` | `0` | Last used tileset spacing | + * | `"tileset.margin"` | `number` | `0` | Last used tileset margin | + * | `"tileset.lastUsedFilter"` | `string` | `""` | Last used tileset file filter | + * | `"tileset.lastUsedFormat"` | `string` | `""` | Last used tileset format | + * | `"resizeMap.removeObjects"` | `boolean` | `true` | Remove objects when resizing | + * | `"exportAsImage.visibleLayersOnly"` | `boolean` | `true` | Export only visible layers | + * | `"exportAsImage.useCurrentScale"` | `boolean` | `false` | Use current scale for export | + * | `"exportAsImage.drawTileGrid"` | `boolean` | `false` | Draw tile grid in export | + * | `"exportAsImage.drawObjectLabels"` | `boolean` | `false` | Draw object labels in export | + * | `"exportAsImage.includeBackgroundColor"` | `boolean` | `false` | Include background color in export | + * | `"automapping.whileDrawing"` | `boolean` | `false` | Automap while drawing | + * | `"mapScene.enableWorlds"` | `boolean` | `true` | Enable worlds in map scene | + * | `"textEdit.monospace"` | `boolean` | `true` | Use monospace font in text editor | + * | `"frame.defaultDuration"` | `number` | `100` | Default animation frame duration | + * | `"console.history"` | `string[]` | `[]` | Console command history | + * | `"property.type"` | `string` | `"string"` | Last used property type | + * | `"file.lastUsedOpenFilter"` | `string` | `""` | Last used file open filter | + * | `"loadedWorlds"` | `string[]` | `[]` | List of currently loaded world files | + * | `"lastUsedTilesetExportFilter"` | `string` | `""` | Last used tileset export filter | + * | `"stampsFolder"` | `string` | `"/stamps"` | Directory for tile stamps | + * + * @since 1.12 + */ +interface Session { + /** + * Path to the current session file. + */ + readonly fileName: string; + + /** + * Returns the session value for the given key, or `defaultValue` if + * the key is not set. + */ + get(key: string, defaultValue?: any): any; + + /** + * Sets the session value for the given key. + */ + set(key: string, value: any): void; + + /** + * Returns whether the given key is present in the session. + */ + isSet(key: string): boolean; + + /** + * Returns the per-file state for the given file name. + */ + fileState(fileName: string): { [key: string]: any }; + + /** + * Sets the per-file state for the given file name. + * + * **Warning:** This replaces the entire per-file state for the given file. + * To set only a single value, use {@link setFileStateValue} instead. + */ + setFileState(fileName: string, fileState: { [key: string]: any }): void; + + /** + * Sets a single value in the per-file state for the given file name. + */ + setFileStateValue(fileName: string, name: string, value: any): void; +} /** * The `tiled` module is the main entry point and provides properties, * functions and signals which are documented below. @@ -4767,6 +4857,18 @@ declare namespace tiled { */ export const tilesetEditor: TilesetEditor; + /** + * Provides access to the current session, allowing scripts to read and + * write session-specific settings like last-used tile size. + * + * In command-line mode (`--evaluate`), no session may be available. In + * that case, methods return safe defaults (empty strings, default + * values, etc.). + * + * @since 1.12 + */ + export const session: Session; + /** * This function can be used to trigger any registered action. This * includes most actions you would normally trigger through the menu or diff --git a/src/tiled/libtilededitor.qbs b/src/tiled/libtilededitor.qbs index 078531ca3b..f9af5409b4 100644 --- a/src/tiled/libtilededitor.qbs +++ b/src/tiled/libtilededitor.qbs @@ -465,6 +465,8 @@ DynamicLibrary { "scriptmodule.h", "scriptprocess.cpp", "scriptprocess.h", + "scriptsession.cpp", + "scriptsession.h", "scriptpropertytype.cpp", "scriptpropertytype.h", "selectionrectangle.cpp", diff --git a/src/tiled/scriptmodule.cpp b/src/tiled/scriptmodule.cpp index b60c6bf519..8badf86075 100644 --- a/src/tiled/scriptmodule.cpp +++ b/src/tiled/scriptmodule.cpp @@ -37,6 +37,7 @@ #include "scriptfileformatwrappers.h" #include "scriptimage.h" #include "scriptmanager.h" +#include "scriptsession.h" #include "tilesetdocument.h" #include "tileseteditor.h" #include "worlddocument.h" @@ -56,6 +57,7 @@ namespace Tiled { ScriptModule::ScriptModule(QObject *parent) : QObject(parent) + , mSession(new ScriptSession(this)) { // If the script module is only created for command-line use, there will // not be a DocumentManager instance. @@ -240,6 +242,11 @@ MapEditor *ScriptModule::mapEditor() const return nullptr; } +ScriptSession *ScriptModule::session() const +{ + return mSession; +} + QColor ScriptModule::color(const QString &name) const { #if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) diff --git a/src/tiled/scriptmodule.h b/src/tiled/scriptmodule.h index 947766035f..94bb53ca71 100644 --- a/src/tiled/scriptmodule.h +++ b/src/tiled/scriptmodule.h @@ -39,6 +39,7 @@ class EditableAsset; class MapEditor; class ScriptImage; class ScriptMapFormatWrapper; +class ScriptSession; class ScriptTilesetFormatWrapper; class ScriptedAction; class ScriptedMapFormat; @@ -70,6 +71,7 @@ class ScriptModule : public QObject Q_PROPERTY(Tiled::EditableAsset *activeAsset READ activeAsset WRITE setActiveAsset NOTIFY activeAssetChanged) Q_PROPERTY(QList openAssets READ openAssets) Q_PROPERTY(Tiled::EditableAsset *project READ project) + Q_PROPERTY(Tiled::ScriptSession *session READ session) Q_PROPERTY(Tiled::MapEditor *mapEditor READ mapEditor) Q_PROPERTY(Tiled::TilesetEditor *tilesetEditor READ tilesetEditor) @@ -105,6 +107,8 @@ class ScriptModule : public QObject TilesetEditor *tilesetEditor() const; MapEditor *mapEditor() const; + ScriptSession *session() const; + Q_INVOKABLE QColor color(const QString &name) const; Q_INVOKABLE QColor color(float r, float g, float b, float a = 1.0f) const; Q_INVOKABLE Tiled::FilePath filePath(const QUrl &path) const; @@ -194,6 +198,7 @@ public slots: std::map> mRegisteredTools; QStringList mScriptArguments; + ScriptSession *mSession = nullptr; }; inline bool ScriptModule::versionLessThan(const QString &a) diff --git a/src/tiled/scriptsession.cpp b/src/tiled/scriptsession.cpp new file mode 100644 index 0000000000..f0aab1aead --- /dev/null +++ b/src/tiled/scriptsession.cpp @@ -0,0 +1,90 @@ +/* + * scriptsession.cpp + * Copyright 2026, Kanishka + * + * 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 "scriptsession.h" + +#include "session.h" + +namespace Tiled { + +ScriptSession::ScriptSession(QObject *parent) + : QObject(parent) +{} + +QString ScriptSession::fileName() const +{ + if (!Session::hasCurrent()) + return QString(); + return Session::current().fileName(); +} + +QVariant ScriptSession::get(const QString &key, + const QVariant &defaultValue) const +{ + if (!Session::hasCurrent()) + return defaultValue; + + const QByteArray latin1Key = key.toLatin1(); + auto &session = Session::current(); + + return session.get(latin1Key.constData(), defaultValue); +} + +void ScriptSession::set(const QString &key, const QVariant &value) +{ + if (!Session::hasCurrent()) + return; + Session::current().set(key.toLatin1().constData(), value); +} + +bool ScriptSession::isSet(const QString &key) const +{ + if (!Session::hasCurrent()) + return false; + return Session::current().isSet(key.toLatin1().constData()); +} + +QVariantMap ScriptSession::fileState(const QString &fileName) const +{ + if (!Session::hasCurrent()) + return {}; + return Session::current().fileState(fileName); +} + +void ScriptSession::setFileState(const QString &fileName, + const QVariantMap &fileState) +{ + if (!Session::hasCurrent()) + return; + Session::current().setFileState(fileName, fileState); +} + +void ScriptSession::setFileStateValue(const QString &fileName, + const QString &name, + const QVariant &value) +{ + if (!Session::hasCurrent()) + return; + Session::current().setFileStateValue(fileName, name, value); +} + +} // namespace Tiled + +#include "moc_scriptsession.cpp" diff --git a/src/tiled/scriptsession.h b/src/tiled/scriptsession.h new file mode 100644 index 0000000000..b3f4c9faf6 --- /dev/null +++ b/src/tiled/scriptsession.h @@ -0,0 +1,59 @@ +/* + * scriptsession.h + * Copyright 2026, Kanishka + * + * 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 +#include +#include + +namespace Tiled { + +/** + * Exposes the current Session to the scripting API as tiled.session. + * + * Provides generic get/set access to session values by their string key, + * as well as access to per-file states. + */ +class ScriptSession : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString fileName READ fileName) + +public: + explicit ScriptSession(QObject *parent = nullptr); + + QString fileName() const; + + Q_INVOKABLE QVariant get(const QString &key, + const QVariant &defaultValue = QVariant()) const; + Q_INVOKABLE void set(const QString &key, const QVariant &value); + Q_INVOKABLE bool isSet(const QString &key) const; + + Q_INVOKABLE QVariantMap fileState(const QString &fileName) const; + Q_INVOKABLE void setFileState(const QString &fileName, + const QVariantMap &fileState); + Q_INVOKABLE void setFileStateValue(const QString &fileName, + const QString &name, + const QVariant &value); +}; + +} // namespace Tiled diff --git a/src/tiled/session.h b/src/tiled/session.h index b161d3e222..a9be13674e 100644 --- a/src/tiled/session.h +++ b/src/tiled/session.h @@ -212,6 +212,7 @@ class TILED_EDITOR_EXPORT Session : protected FileHelper static Session &initialize(); static Session ¤t(); + static bool hasCurrent() { return mCurrent != nullptr; } static Session &switchCurrent(const QString &fileName); static void deinitialize();