diff -Nru tiled-qt-0.7.1/AUTHORS tiled-qt-0.8.0/AUTHORS --- tiled-qt-0.7.1/AUTHORS 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/AUTHORS 2011-12-11 21:49:29.000000000 +0000 @@ -12,10 +12,11 @@ Michael Woerister (improvements to the object properties dialog) Roderic Morris (added many icons and initial object group support) Stefan Beller (added the automap feature, quick stamps, lines/circles - support for multiple maps) + support for multiple maps, random stamps) Translators: +Alexander Komarnitsky (Russian) Antonio Ricci (Italian) Bin Wu (Chinese) Hiroki Utsunomiya (Japanese) @@ -39,6 +40,9 @@ Licensed under a Creative Commons Attribution 3.0 license. See http://p.yusukekamiyamane.com/ +The dice icon is based on an SVG by Steaphan Greene that I found on Wikipedia: +http://en.wikipedia.org/wiki/File:2-Dice-Icon.svg + Tilesets: perspective_walls.png - (C) Clint Bellanger, released as Public Domain diff -Nru tiled-qt-0.7.1/debian/changelog tiled-qt-0.8.0/debian/changelog --- tiled-qt-0.7.1/debian/changelog 2011-10-10 06:28:58.000000000 +0000 +++ tiled-qt-0.8.0/debian/changelog 2011-12-16 15:37:43.000000000 +0000 @@ -1,3 +1,9 @@ +tiled-qt (0.8.0-1) unstable; urgency=low + + * New upstream release + + -- Ying-Chun Liu (PaulLiu) Fri, 16 Dec 2011 23:37:43 +0800 + tiled-qt (0.7.1-1) unstable; urgency=low * New upstream release diff -Nru tiled-qt-0.7.1/debian/patches/addrpath.patch tiled-qt-0.8.0/debian/patches/addrpath.patch --- tiled-qt-0.7.1/debian/patches/addrpath.patch 2011-02-07 16:34:36.000000000 +0000 +++ tiled-qt-0.8.0/debian/patches/addrpath.patch 2011-12-16 15:38:23.000000000 +0000 @@ -5,10 +5,10 @@ Author: Ying-Chun Liu (PaulLiu) Last-Update: 2011-02-07 --- -Index: tiled-qt-0.6.0/src/tiled/tiled.pro +Index: tiled-qt-0.8.0/src/tiled/tiled.pro =================================================================== ---- tiled-qt-0.6.0.orig/src/tiled/tiled.pro 2011-02-07 22:09:05.564154770 +0800 -+++ tiled-qt-0.6.0/src/tiled/tiled.pro 2011-02-07 22:09:09.267821781 +0800 +--- tiled-qt-0.8.0.orig/src/tiled/tiled.pro 2011-12-12 05:49:29.000000000 +0800 ++++ tiled-qt-0.8.0/src/tiled/tiled.pro 2011-12-16 23:38:17.982211073 +0800 @@ -25,7 +25,7 @@ # Make sure the Tiled executable can find libtiled @@ -18,10 +18,10 @@ # It is not possible to use ORIGIN in QMAKE_RPATHDIR, so a bit manually QMAKE_LFLAGS += -Wl,-z,origin \'-Wl,-rpath,$$join(QMAKE_RPATHDIR, ":")\' -Index: tiled-qt-0.6.0/src/plugins/plugin.pri +Index: tiled-qt-0.8.0/src/plugins/plugin.pri =================================================================== ---- tiled-qt-0.6.0.orig/src/plugins/plugin.pri 2011-02-07 22:10:49.039820473 +0800 -+++ tiled-qt-0.6.0/src/plugins/plugin.pri 2011-02-07 22:24:16.167834631 +0800 +--- tiled-qt-0.8.0.orig/src/plugins/plugin.pri 2011-12-12 05:49:29.000000000 +0800 ++++ tiled-qt-0.8.0/src/plugins/plugin.pri 2011-12-16 23:38:17.982211073 +0800 @@ -30,7 +30,7 @@ # Set rpath so that the plugin will resolve libtiled correctly @@ -31,10 +31,10 @@ # It is not possible to use ORIGIN in QMAKE_RPATHDIR, so a bit manually QMAKE_LFLAGS += -Wl,-z,origin \'-Wl,-rpath,$$join(QMAKE_RPATHDIR, ":")\' -Index: tiled-qt-0.6.0/src/tmxviewer/tmxviewer.pro +Index: tiled-qt-0.8.0/src/tmxviewer/tmxviewer.pro =================================================================== ---- tiled-qt-0.6.0.orig/src/tmxviewer/tmxviewer.pro 2011-02-07 22:24:47.455820255 +0800 -+++ tiled-qt-0.6.0/src/tmxviewer/tmxviewer.pro 2011-02-07 22:24:58.679833967 +0800 +--- tiled-qt-0.8.0.orig/src/tmxviewer/tmxviewer.pro 2011-12-12 05:49:29.000000000 +0800 ++++ tiled-qt-0.8.0/src/tmxviewer/tmxviewer.pro 2011-12-16 23:38:17.982211073 +0800 @@ -22,7 +22,7 @@ # Make sure the executable can find libtiled diff -Nru tiled-qt-0.7.1/dist/make-dist-mac.sh tiled-qt-0.8.0/dist/make-dist-mac.sh --- tiled-qt-0.7.1/dist/make-dist-mac.sh 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/dist/make-dist-mac.sh 2011-12-11 21:49:29.000000000 +0000 @@ -1,8 +1,9 @@ #!/bin/bash # This script generates a mac release from an already compiled Tiled.app in # the bin folder. You should compile the release before running this: -# qmake -r -spec macx-g++ CONFIG+=release CONFIG+=x86 CONFIG+=x86_64 -# make +# tiled$ qmake -r -spec macx-g++ CONFIG+=release CONFIG+=x86_64 +# tiled$ make +# tiled$ ./dist/make-dist-mac.sh version # Get the version if [ "$#" -eq "0" ]; then @@ -16,19 +17,21 @@ binDir="$baseDir/bin" # Create a temporary staging directory +echo Creating staging directory tempDir=`mktemp -d /tmp/${name}.XXXXXX` || exit 1 -echo $tempDir +echo # Copy things to temp directory +echo Copying files cp "$baseDir/AUTHORS" "$tempDir/" cp "$baseDir/COPYING" "$tempDir/" cp "$baseDir/NEWS" "$tempDir/" cp "$baseDir/README.md" "$tempDir/" cp -R "$baseDir/examples" "$tempDir/" cp -R "$binDir/Tiled.app" "$tempDir/" - -# Create symlink to application directory -ln -s /Applications "$tempDir/Applications" +ln -s /Applications "$tempDir/Applications" #Symlink to Applciations for easy install +cp "$baseDir/src/tiled/images/tmx-icon-mac.icns" "$tempDir/Tiled.app/Contents/Resources/" +echo # Get various in-bundle directories pluginsDir="$tempDir/Tiled.app/Contents/PlugIns" @@ -36,23 +39,30 @@ frameworksDir="$tempDir/Tiled.app/Contents/Frameworks" # Use macdeployqt to copy Qt frameworks to the app +echo Running macdeployqt macdeployqt "$tempDir/Tiled.app" -qtCoreDir="$frameworksDir/QtCore.framework" -qtGuiDir="$frameworksDir/QtGui.framework" +echo -# Modify plugins to use Qt frameworks contained within the app bundle (perhaps theres some way to get macdeployqt to do this?) -install_name_tool -change "QtCore.framework/Versions/4/QtCore" "@executable_path/../Frameworks/QtCore.framework/Versions/4/QtCore" "$frameworksDir/libtiled.dylib" -install_name_tool -change "QtCore.framework/Versions/4/QtCore" "@executable_path/../Frameworks/QtCore.framework/Versions/4/QtCore" "$pluginsDir/libtmw.dylib" -install_name_tool -change "QtCore.framework/Versions/4/QtCore" "@executable_path/../Frameworks/QtCore.framework/Versions/4/QtCore" "$pluginsDir/libtengine.dylib" -install_name_tool -change "QtGui.framework/Versions/4/QtGui" "@executable_path/../Frameworks/QtGui.framework/Versions/4/QtGui" "$frameworksDir/libtiled.dylib" -install_name_tool -change "QtGui.framework/Versions/4/QtGui" "@executable_path/../Frameworks/QtGui.framework/Versions/4/QtGui" "$pluginsDir/libtmw.dylib" -install_name_tool -change "QtGui.framework/Versions/4/QtGui" "@executable_path/../Frameworks/QtGui.framework/Versions/4/QtGui" "$pluginsDir/libtengine.dylib" - -# Copy tmx icon to the application -cp "$baseDir/src/tiled/images/tmx-icon-mac.icns" "$tempDir/Tiled.app/Contents/Resources/" +# Modify plugins to use Qt frameworks contained within the app bundle (is there some way to get macdeployqt to do this?) +echo Fixing plugins +for plugin in $(echo $(ls $frameworksDir/*.dylib) $(ls $pluginsDir/*.dylib) ) +do + pluginname=`basename $plugin` + echo " Fixing $pluginname" + install_name_tool -change "$QTDIR"lib/QtCore.framework/Versions/4/QtCore "@executable_path/../Frameworks/QtCore.framework/Versions/4/QtCore" "$plugin" + install_name_tool -change "$QTDIR"lib/QtGui.framework/Versions/4/QtGui "@executable_path/../Frameworks/QtGui.framework/Versions/4/QtGui" "$plugin" +done +echo # Create dmg from the temp directory +echo Creating dmg file hdiutil create "$baseDir/$name.dmg" -srcfolder "$tempDir" -volname "Tiled $1" +echo # Delete the temp directory +echo Removing staging directory $tempDir rm -rf "$tempDir" +echo + +# Exit +echo Done diff -Nru tiled-qt-0.7.1/dist/win/qt.conf tiled-qt-0.8.0/dist/win/qt.conf --- tiled-qt-0.7.1/dist/win/qt.conf 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/dist/win/qt.conf 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,3 @@ +[Paths] +Plugins = plugins +Translations = translations diff -Nru tiled-qt-0.7.1/dist/win/README.txt tiled-qt-0.8.0/dist/win/README.txt --- tiled-qt-0.7.1/dist/win/README.txt 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/dist/win/README.txt 2011-12-11 21:49:29.000000000 +0000 @@ -25,9 +25,9 @@ to read the content of a file "version.txt" located in the root directory of tiled into that variable. The commands to set those may look like the following: -set QTDIR="C:\Qt\4.7.1" +set QTDIR="C:\Qt\4.7.4" set MINGW="C:\MinGW" -set VERSION="0.7.1" +set VERSION="0.8.0" Optionally you can also set the program architecture which is then used to deduce the resulting installer filename. It can either be 32 or 64 and defaults diff -Nru tiled-qt-0.7.1/dist/win/tiled.nsi tiled-qt-0.8.0/dist/win/tiled.nsi --- tiled-qt-0.7.1/dist/win/tiled.nsi 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/dist/win/tiled.nsi 2011-12-11 21:49:29.000000000 +0000 @@ -185,6 +185,7 @@ File ${QT_DIR}\bin\QtGui4.dll File ${QT_DIR}\bin\QtOpenGL4.dll File ${ROOT_DIR}\src\tiled\images\tiled-icon.ico +File ${ROOT_DIR}\dist\win\qt.conf SetOutPath $INSTDIR\plugins\codecs File ${QT_DIR}\plugins\codecs\qcncodecs4.dll @@ -202,15 +203,16 @@ SetOutPath $INSTDIR\translations File ${ROOT_DIR}\translations\*.qm -;File ${QT_DIR}\translations\qt_cs.qm -;File ${QT_DIR}\translations\qt_de.qm -;File ${QT_DIR}\translations\qt_es.qm -;File ${QT_DIR}\translations\qt_fr.qm -;File ${QT_DIR}\translations\qt_he.qm -;File ${QT_DIR}\translations\qt_ja.qm -;File ${QT_DIR}\translations\qt_pt.qm -;File ${QT_DIR}\translations\qt_zh_CN.qm -;File ${QT_DIR}\translations\qt_zh_TW.qm +File ${QT_DIR}\translations\qt_cs.qm +File ${QT_DIR}\translations\qt_de.qm +File ${QT_DIR}\translations\qt_es.qm +File ${QT_DIR}\translations\qt_fr.qm +File ${QT_DIR}\translations\qt_he.qm +File ${QT_DIR}\translations\qt_ja.qm +File ${QT_DIR}\translations\qt_pt.qm +File ${QT_DIR}\translations\qt_ru.qm +File ${QT_DIR}\translations\qt_zh_CN.qm +File ${QT_DIR}\translations\qt_zh_TW.qm SetOutPath $INSTDIR\examples File /r ${ROOT_DIR}\examples\*.* diff -Nru tiled-qt-0.7.1/docs/Doxyfile tiled-qt-0.8.0/docs/Doxyfile --- tiled-qt-0.7.1/docs/Doxyfile 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/docs/Doxyfile 2011-12-11 21:49:29.000000000 +0000 @@ -89,7 +89,7 @@ INPUT = ../src INPUT_ENCODING = UTF-8 FILE_PATTERNS = -RECURSIVE = NO +RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = */moc_*.cpp \ diff -Nru tiled-qt-0.7.1/examples/isometric_grass_and_water.tmx tiled-qt-0.8.0/examples/isometric_grass_and_water.tmx --- tiled-qt-0.7.1/examples/isometric_grass_and_water.tmx 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/examples/isometric_grass_and_water.tmx 2011-12-11 21:49:29.000000000 +0000 @@ -2,6 +2,7 @@ + diff -Nru tiled-qt-0.7.1/examples/perspective_walls.tmx tiled-qt-0.8.0/examples/perspective_walls.tmx --- tiled-qt-0.7.1/examples/perspective_walls.tmx 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/examples/perspective_walls.tmx 2011-12-11 21:49:29.000000000 +0000 @@ -1,5 +1,5 @@ - + diff -Nru tiled-qt-0.7.1/examples/perspective_walls.tsx tiled-qt-0.8.0/examples/perspective_walls.tsx --- tiled-qt-0.7.1/examples/perspective_walls.tsx 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/examples/perspective_walls.tsx 2011-12-11 21:49:29.000000000 +0000 @@ -1,5 +1,6 @@ + diff -Nru tiled-qt-0.7.1/.gitignore tiled-qt-0.8.0/.gitignore --- tiled-qt-0.7.1/.gitignore 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/.gitignore 2011-12-11 21:49:29.000000000 +0000 @@ -1,6 +1,5 @@ bin/tiled bin/tmxviewer -bin/*.dll lib/tiled/plugins/* lib/*.so* Makefile* @@ -9,8 +8,18 @@ *.moc moc_*.cpp ui_*.h -*.pro.user +*.pro.user* qrc_* docs/html +# Build artifacts on Windows +tiled.exe +tmxviewer.exe +*.dll +*.obj +*.a +*.lib +*.exp +*.intermediate.manifest + tests/test_mapreader diff -Nru tiled-qt-0.7.1/.LICENSE-HEADER tiled-qt-0.8.0/.LICENSE-HEADER --- tiled-qt-0.7.1/.LICENSE-HEADER 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/.LICENSE-HEADER 2011-12-11 21:49:29.000000000 +0000 @@ -1,6 +1,6 @@ /* * %FILENAME% - * Copyright 2010, Your Name + * Copyright 2011, Your Name * * This file is part of Tiled. * diff -Nru tiled-qt-0.7.1/NEWS tiled-qt-0.8.0/NEWS --- tiled-qt-0.7.1/NEWS 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/NEWS 2011-12-11 21:49:29.000000000 +0000 @@ -1,3 +1,27 @@ +0.8.0 (11 December 2011) +* Added support for polygon and polyline objects +* Added support for tile rotation +* Added support for defining the color of custom object types +* Added a Delete action to delete selected tiles or objects +* Added random mode to the stamp brush +* Added Flare export plugin +* Added JSON plugin that supports both reading and writing +* Added ability to rename tilesets +* Added a mode in which the current layer is highlighted +* Added support for specifying a tile drawing offset +* Added a shortcut to copy the current tile position to clipboard (Alt+C) +* Added a command line option to disable OpenGL +* Allow custom properties on tilesets +* Many automapping improvements +* Improved tileset dock to handle a large amount of tilesets better +* Made the 'Show Grid' option in the tileset view persistent +* Raised the tile size limit in the New Tileset dialog from 999 to 9999 +* Correctly handle changes in the width of a tileset image +* Worked around a long standing crash bug +* Added Russian translation +* Updated the German, Japanese, Spanish, Chinese, Czech, Dutch, French and + Brazilian Portuguese translations + 0.7.1 (27 September 2011) * Select stamp tool when selecting tiles in tileset view * Enable anti-aliasing for OpenGL mode diff -Nru tiled-qt-0.7.1/README.md tiled-qt-0.8.0/README.md --- tiled-qt-0.7.1/README.md 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/README.md 2011-12-11 21:49:29.000000000 +0000 @@ -26,7 +26,7 @@ Make sure the Qt (>= 4.6) development libraries are installed: -* In Ubuntu/Debian: `sudo apt-get install libqt4-dev libqt4-opengl-dev` +* In Ubuntu/Debian: `sudo apt-get install libqt4-dev libqt4-opengl-dev zlib1g-dev` * In Fedora: `yum install qt-devel` * In Arch Linux: `pacman -S qt` diff -Nru tiled-qt-0.7.1/src/libtiled/gidmapper.cpp tiled-qt-0.8.0/src/libtiled/gidmapper.cpp --- tiled-qt-0.7.1/src/libtiled/gidmapper.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/gidmapper.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -27,13 +27,23 @@ using namespace Tiled; // Bits on the far end of the 32-bit global tile ID are used for tile flags -const int FlippedHorizontallyFlag = 0x80000000; -const int FlippedVerticallyFlag = 0x40000000; +const int FlippedHorizontallyFlag = 0x80000000; +const int FlippedVerticallyFlag = 0x40000000; +const int FlippedAntiDiagonallyFlag = 0x20000000; GidMapper::GidMapper() { } +GidMapper::GidMapper(const QList &tilesets) +{ + uint firstGid = 1; + foreach (Tileset *tileset, tilesets) { + insert(firstGid, tileset); + firstGid += tileset->tileCount(); + } +} + Cell GidMapper::gidToCell(uint gid, bool &ok) const { Cell result; @@ -41,9 +51,12 @@ // Read out the flags result.flippedHorizontally = (gid & FlippedHorizontallyFlag); result.flippedVertically = (gid & FlippedVerticallyFlag); + result.flippedAntiDiagonally = (gid & FlippedAntiDiagonallyFlag); // Clear the flags - gid &= ~(FlippedHorizontallyFlag | FlippedVerticallyFlag); + gid &= ~(FlippedHorizontallyFlag | + FlippedVerticallyFlag | + FlippedAntiDiagonallyFlag); if (gid == 0) { ok = true; @@ -53,10 +66,23 @@ // Find the tileset containing this tile QMap::const_iterator i = mFirstGidToTileset.upperBound(gid); --i; // Navigate one tileset back since upper bound finds the next - const int tileId = gid - i.key(); + int tileId = gid - i.key(); const Tileset *tileset = i.value(); - result.tile = tileset ? tileset->tileAt(tileId) : 0; + if (tileset) { + const int columnCount = mTilesetColumnCounts.value(tileset); + if (columnCount > 0 && columnCount != tileset->columnCount()) { + // Correct tile index for changes in image width + const int row = tileId / columnCount; + const int column = tileId % columnCount; + tileId = row * tileset->columnCount() + column; + } + + result.tile = tileset->tileAt(tileId); + } else { + result.tile = 0; + } + ok = true; } @@ -84,6 +110,16 @@ gid |= FlippedHorizontallyFlag; if (cell.flippedVertically) gid |= FlippedVerticallyFlag; + if (cell.flippedAntiDiagonally) + gid |= FlippedAntiDiagonallyFlag; return gid; } + +void GidMapper::setTilesetWidth(const Tileset *tileset, int width) +{ + if (tileset->tileWidth() == 0) + return; + + mTilesetColumnCounts.insert(tileset, tileset->columnCountForWidth(width)); +} diff -Nru tiled-qt-0.7.1/src/libtiled/gidmapper.h tiled-qt-0.8.0/src/libtiled/gidmapper.h --- tiled-qt-0.7.1/src/libtiled/gidmapper.h 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/gidmapper.h 2011-12-11 21:49:29.000000000 +0000 @@ -40,6 +40,11 @@ GidMapper(); /** + * Constructor that initializes the gid mapper using the given \a tilesets. + */ + GidMapper(const QList &tilesets); + + /** * Insert the given \a tileset with \a firstGid as its first global ID. */ void insert(uint firstGid, Tileset *tileset) @@ -67,8 +72,16 @@ */ uint cellToGid(const Cell &cell) const; + /** + * This sets the original tileset width. In case the image size has + * changed, the tile indexes will be adjusted automatically when using + * gidToCell(). + */ + void setTilesetWidth(const Tileset *tileset, int width); + private: QMap mFirstGidToTileset; + QMap mTilesetColumnCounts; }; } // namespace Tiled diff -Nru tiled-qt-0.7.1/src/libtiled/isometricrenderer.cpp tiled-qt-0.8.0/src/libtiled/isometricrenderer.cpp --- tiled-qt-0.7.1/src/libtiled/isometricrenderer.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/isometricrenderer.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -1,6 +1,6 @@ /* * isometricrenderer.cpp - * Copyright 2009-2010, Thorbjørn Lindeijer + * Copyright 2009-2011, Thorbjørn Lindeijer * * This file is part of libtiled. * @@ -32,6 +32,7 @@ #include "mapobject.h" #include "tile.h" #include "tilelayer.h" +#include "tileset.h" #include @@ -71,6 +72,11 @@ bottomCenter.y() - img.height(), img.width(), img.height()).adjusted(-1, -1, 1, 1); + } else if (!object->polygon().isEmpty()) { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + const QPolygonF screenPolygon = tileToPixelCoords(polygon); + return screenPolygon.boundingRect().adjusted(-2, -2, 3, 3); } else { // Take the bounding rect of the projected object, and then add a few // pixels on all sides to correct for the line width. @@ -82,10 +88,31 @@ QPainterPath IsometricRenderer::shape(const MapObject *object) const { QPainterPath path; - if (object->tile()) + if (object->tile()) { path.addRect(boundingRect(object)); - else - path.addPolygon(tileRectToPolygon(object->bounds())); + } else { + switch (object->shape()) { + case MapObject::Rectangle: + path.addPolygon(tileRectToPolygon(object->bounds())); + break; + case MapObject::Polygon: + case MapObject::Polyline: { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + const QPolygonF screenPolygon = tileToPixelCoords(polygon); + if (object->shape() == MapObject::Polygon) { + path.addPolygon(screenPolygon); + } else { + for (int i = 1; i < screenPolygon.size(); ++i) { + path.addPolygon(lineToPolygon(screenPolygon[i - 1], + screenPolygon[i])); + } + path.setFillRule(Qt::WindingFill); + } + break; + } + } + } return path; } @@ -138,10 +165,14 @@ if (rect.isNull()) rect = boundingRect(layer->bounds()); - const QSize maxTileSize = layer->maxTileSize(); - const int extraWidth = maxTileSize.width() - tileWidth; - const int extraHeight = maxTileSize.height() - tileHeight; - rect.adjust(-extraWidth, 0, 0, extraHeight); + QMargins drawMargins = layer->drawMargins(); + drawMargins.setTop(drawMargins.top() - tileHeight); + drawMargins.setRight(drawMargins.right() - tileWidth); + + rect.adjust(-drawMargins.right(), + -drawMargins.bottom(), + drawMargins.left(), + drawMargins.top()); // Determine the tile and pixel coordinates to start at QPointF tilePos = pixelToTileCoords(rect.x(), rect.y()); @@ -176,6 +207,8 @@ // Determine whether the current row is shifted half a tile to the right bool shifted = inUpperHalf ^ inLeftHalf; + QTransform baseTransform = painter->transform(); + for (int y = startPos.y(); y - tileHeight < rect.bottom(); y += tileHeight / 2) { @@ -186,16 +219,42 @@ const Cell &cell = layer->cellAt(columnItr); if (!cell.isEmpty()) { const QPixmap &img = cell.tile->image(); - int flipX = cell.flippedHorizontally ? -1 : 1; - int flipY = cell.flippedVertically ? -1 : 1; - int offsetX = cell.flippedHorizontally ? img.width() : 0; - int offsetY = cell.flippedVertically ? 0 : img.height(); - - painter->scale(flipX, flipY); - painter->drawPixmap(x * flipX - offsetX, - y * flipY - offsetY, - img); - painter->scale(flipX, flipY); + const QPoint offset = cell.tile->tileset()->tileOffset(); + + qreal m11 = 1; // Horizontal scaling factor + qreal m12 = 0; // Vertical shearing factor + qreal m21 = 0; // Horizontal shearing factor + qreal m22 = 1; // Vertical scaling factor + qreal dx = offset.x() + x; + qreal dy = offset.y() + y - img.height(); + + if (cell.flippedAntiDiagonally) { + // Use shearing to swap the X/Y axis + m11 = 0; + m12 = 1; + m21 = 1; + m22 = 0; + + // Compensate for the swap of image dimensions + dy += img.height() - img.width(); + } + if (cell.flippedHorizontally) { + m11 = -m11; + m21 = -m21; + dx += cell.flippedAntiDiagonally ? img.height() + : img.width(); + } + if (cell.flippedVertically) { + m12 = -m12; + m22 = -m22; + dy += cell.flippedAntiDiagonally ? img.width() + : img.height(); + } + + const QTransform transform(m11, m12, m21, m22, dx, dy); + painter->setTransform(transform * baseTransform); + + painter->drawPixmap(0, 0, img); } } @@ -215,6 +274,8 @@ shifted = false; } } + + painter->setTransform(baseTransform); } void IsometricRenderer::drawTileSelection(QPainter *painter, @@ -267,18 +328,49 @@ // TODO: Draw the object name // TODO: Do something sensible to make null-sized objects usable - QPolygonF polygon = tileRectToPolygon(object->bounds()); + switch (object->shape()) { + case MapObject::Rectangle: { + QPolygonF polygon = tileRectToPolygon(object->bounds()); + painter->drawPolygon(polygon); + + pen.setColor(color); + painter->setPen(pen); + painter->setBrush(brush); + polygon.translate(0, -1); - // Make sure the line aligns nicely on the pixels - if (pen.width() % 2) - painter->translate(0.5, 0.5); + painter->drawPolygon(polygon); + break; + } + case MapObject::Polygon: { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + QPolygonF screenPolygon = tileToPixelCoords(polygon); + + painter->drawPolygon(screenPolygon); + + pen.setColor(color); + painter->setPen(pen); + painter->setBrush(brush); + screenPolygon.translate(0, -1); - painter->drawPolygon(polygon); - pen.setColor(color); - painter->setPen(pen); - painter->setBrush(brush); - polygon.translate(0, -1); - painter->drawPolygon(polygon); + painter->drawPolygon(screenPolygon); + break; + } + case MapObject::Polyline: { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + QPolygonF screenPolygon = tileToPixelCoords(polygon); + + painter->drawPolyline(screenPolygon); + + pen.setColor(color); + painter->setPen(pen); + screenPolygon.translate(0, -1); + + painter->drawPolyline(screenPolygon); + break; + } + } } painter->restore(); diff -Nru tiled-qt-0.7.1/src/libtiled/libtiled.pro tiled-qt-0.8.0/src/libtiled/libtiled.pro --- tiled-qt-0.7.1/src/libtiled/libtiled.pro 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/libtiled.pro 2011-12-11 21:49:29.000000000 +0000 @@ -12,8 +12,13 @@ } DLLDESTDIR = ../.. -win32:INCLUDEPATH += $$(QTDIR)/src/3rdparty/zlib -else:LIBS += -lz +win32 { + # It is enough to include zlib, since the symbols are available in Qt + INCLUDEPATH += ../zlib +} else { + # On other platforms it is necessary to link to zlib explicitly + LIBS += -lz +} DEFINES += QT_NO_CAST_FROM_ASCII \ QT_NO_CAST_TO_ASCII @@ -26,6 +31,7 @@ map.cpp \ mapobject.cpp \ mapreader.cpp \ + maprenderer.cpp \ mapwriter.cpp \ objectgroup.cpp \ orthogonalrenderer.cpp \ diff -Nru tiled-qt-0.7.1/src/libtiled/map.cpp tiled-qt-0.8.0/src/libtiled/map.cpp --- tiled-qt-0.7.1/src/libtiled/map.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/map.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -43,8 +43,7 @@ mWidth(width), mHeight(height), mTileWidth(tileWidth), - mTileHeight(tileHeight), - mMaxTileSize(tileWidth, tileHeight) + mTileHeight(tileHeight) { } @@ -53,12 +52,25 @@ qDeleteAll(mLayers); } -void Map::adjustMaxTileSize(const QSize &size) +static QMargins maxMargins(const QMargins &a, + const QMargins &b) { - if (size.width() > mMaxTileSize.width()) - mMaxTileSize.setWidth(size.width()); - if (size.height() > mMaxTileSize.height()) - mMaxTileSize.setHeight(size.height()); + return QMargins(qMax(a.left(), b.left()), + qMax(a.top(), b.top()), + qMax(a.right(), b.right()), + qMax(a.bottom(), b.bottom())); +} + +void Map::adjustDrawMargins(const QMargins &margins) +{ + // The TileLayer includes the maximum tile size in its draw margins. So + // we need to subtract the tile size of the map, since that part does not + // contribute to additional margin. + mDrawMargins = maxMargins(QMargins(margins.left(), + margins.top() - mTileHeight, + margins.right() - mTileWidth, + margins.bottom()), + mDrawMargins); } int Map::tileLayerCount() const @@ -105,7 +117,7 @@ layer->setMap(this); if (TileLayer *tileLayer = dynamic_cast(layer)) - adjustMaxTileSize(tileLayer->maxTileSize()); + adjustDrawMargins(tileLayer->drawMargins()); } Layer *Map::takeLayerAt(int index) @@ -158,10 +170,38 @@ Map *Map::clone() const { Map *o = new Map(mOrientation, mWidth, mHeight, mTileWidth, mTileHeight); - o->mMaxTileSize = mMaxTileSize; - foreach (Layer *layer, mLayers) + o->mDrawMargins = mDrawMargins; + foreach (const Layer *layer, mLayers) o->addLayer(layer->clone()); o->mTilesets = mTilesets; o->setProperties(properties()); return o; } + + +QString Tiled::orientationToString(Map::Orientation orientation) +{ + switch (orientation) { + default: + case Map::Unknown: + return QLatin1String("unknown"); + break; + case Map::Orthogonal: + return QLatin1String("orthogonal"); + break; + case Map::Isometric: + return QLatin1String("isometric"); + break; + } +} + +Map::Orientation Tiled::orientationFromString(const QString &string) +{ + Map::Orientation orientation = Map::Unknown; + if (string == QLatin1String("orthogonal")) { + orientation = Map::Orthogonal; + } else if (string == QLatin1String("isometric")) { + orientation = Map::Isometric; + } + return orientation; +} diff -Nru tiled-qt-0.7.1/src/libtiled/map.h tiled-qt-0.8.0/src/libtiled/map.h --- tiled-qt-0.7.1/src/libtiled/map.h 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/map.h 2011-12-11 21:49:29.000000000 +0000 @@ -34,6 +34,7 @@ #include "object.h" #include +#include #include namespace Tiled { @@ -65,8 +66,7 @@ enum Orientation { Unknown, Orthogonal, - Isometric, - Hexagonal + Isometric }; /** @@ -128,26 +128,18 @@ int tileHeight() const { return mTileHeight; } /** - * Returns the maximum tile size used by tile layers of this map. - * @see TileLayer::extraTileSize() + * Adjusts the draw margins to be at least as big as the given margins. + * Called from tile layers when their tiles change. */ - QSize maxTileSize() const { return mMaxTileSize; } + void adjustDrawMargins(const QMargins &margins); /** - * Adjusts the maximum tile size to be at least as much as the given - * size. Called from tile layers when their maximum tile size increases. - */ - void adjustMaxTileSize(const QSize &size); - - /** - * Convenience method for getting the extra tile size, which is the number - * of pixels that tiles may extend beyond the size of the tile grid. + * Returns the margins that have to be taken into account when figuring + * out which part of the map to repaint after changing some tiles. * - * @see maxTileSize() + * @see TileLayer::drawMargins */ - QSize extraTileSize() const - { return QSize(mMaxTileSize.width() - mTileWidth, - mMaxTileSize.height() - mTileHeight); } + QMargins drawMargins() const { return mDrawMargins; } /** * Returns the number of layers of this map. @@ -259,11 +251,28 @@ int mHeight; int mTileWidth; int mTileHeight; - QSize mMaxTileSize; + QMargins mDrawMargins; QList mLayers; QList mTilesets; }; +/** + * Helper function that converts the map orientation to a string value. Useful + * for map writers. + * + * @return The map orientation as a lowercase string. + */ +TILEDSHARED_EXPORT QString orientationToString(Map::Orientation); + +/** + * Helper function that converts a string to a map orientation enumerator. + * Useful for map readers. + * + * @return The map orientation matching the given string, or Map::Unknown if + * the string is unrecognized. + */ +TILEDSHARED_EXPORT Map::Orientation orientationFromString(const QString &); + } // namespace Tiled #endif // MAP_H diff -Nru tiled-qt-0.7.1/src/libtiled/mapobject.cpp tiled-qt-0.8.0/src/libtiled/mapobject.cpp --- tiled-qt-0.7.1/src/libtiled/mapobject.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/mapobject.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -33,18 +33,20 @@ MapObject::MapObject(): mSize(0, 0), + mShape(Rectangle), mTile(0), mObjectGroup(0) { } MapObject::MapObject(const QString &name, const QString &type, - qreal x, qreal y, - qreal width, qreal height): + const QPointF &pos, + const QSizeF &size): mName(name), - mPos(x, y), - mSize(width, height), mType(type), + mPos(pos), + mSize(size), + mShape(Rectangle), mTile(0), mObjectGroup(0) { @@ -52,10 +54,10 @@ MapObject *MapObject::clone() const { - MapObject *o = new MapObject(mName, mType, - mPos.x(), mPos.y(), - mSize.width(), mSize.height()); + MapObject *o = new MapObject(mName, mType, mPos, mSize); o->setProperties(properties()); + o->setPolygon(mPolygon); + o->setShape(mShape); o->setTile(mTile); return o; } diff -Nru tiled-qt-0.7.1/src/libtiled/mapobject.h tiled-qt-0.8.0/src/libtiled/mapobject.h --- tiled-qt-0.7.1/src/libtiled/mapobject.h 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/mapobject.h 2011-12-11 21:49:29.000000000 +0000 @@ -33,7 +33,7 @@ #include "object.h" -#include +#include #include #include #include @@ -56,6 +56,17 @@ { public: /** + * Enumerates the different object shapes. Rectangle is the default shape. + * When a polygon is set, the shape determines whether it should be + * interpreted as a filled polygon or a line. + */ + enum Shape { + Rectangle, + Polygon, + Polyline + }; + + /** * Default constructor. */ MapObject(); @@ -64,8 +75,8 @@ * Constructor. */ MapObject(const QString &name, const QString &type, - qreal x, qreal y, - qreal width, qreal height); + const QPointF &pos, + const QSizeF &size); /** * Destructor. @@ -84,6 +95,17 @@ void setName(const QString &name) { mName = name; } /** + * Returns the type of this object. The type usually says something about + * how the object is meant to be interpreted by the engine. + */ + const QString &type() const { return mType; } + + /** + * Sets the type of this object. + */ + void setType(const QString &type) { mType = type; } + + /** * Returns the position of this object. */ const QPointF &position() const { return mPos; } @@ -147,24 +169,39 @@ void setHeight(qreal height) { mSize.setHeight(height); } /** - * Shortcut to getting a QRectF from position() and size(). + * Sets the polygon associated with this object. The polygon is only used + * when the object shape is set to either Polygon or Polyline. + * + * \sa setShape() */ - QRectF bounds() const { return QRectF(mPos, mSize); } + void setPolygon(const QPolygonF &polygon) { mPolygon = polygon; } /** - * Returns the type of this object. The type usually says something about - * how the object is meant to be interpreted by the engine. + * Returns the polygon associated with this object. Returns an empty + * polygon when no polygon is associated with this object. */ - const QString &type() const { return mType; } + const QPolygonF &polygon() const { return mPolygon; } /** - * Sets the type of this object. + * Sets the shape of the object. */ - void setType(const QString &type) { mType = type; } + void setShape(Shape shape) { mShape = shape; } + + /** + * Returns the shape of the object. + */ + Shape shape() const { return mShape; } + + /** + * Shortcut to getting a QRectF from position() and size(). + */ + QRectF bounds() const { return QRectF(mPos, mSize); } /** * Sets the tile that is associated with this object. The object will * display as the tile image. + * + * \warning The object shape is ignored for tile objects! */ void setTile(Tile *tile) { mTile = tile; } @@ -193,9 +230,11 @@ private: QString mName; + QString mType; QPointF mPos; QSizeF mSize; - QString mType; + QPolygonF mPolygon; + Shape mShape; Tile *mTile; ObjectGroup *mObjectGroup; }; diff -Nru tiled-qt-0.7.1/src/libtiled/mapreader.cpp tiled-qt-0.8.0/src/libtiled/mapreader.cpp --- tiled-qt-0.7.1/src/libtiled/mapreader.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/mapreader.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -97,6 +97,7 @@ ObjectGroup *readObjectGroup(); MapObject *readObject(); + QPolygonF readPolygon(); Properties readProperties(); void readProperty(Properties *properties); @@ -182,17 +183,6 @@ xml.skipCurrentElement(); } -static Map::Orientation orientationFromString(const QStringRef &string) -{ - Map::Orientation orientation = Map::Unknown; - if (string == QLatin1String("orthogonal")) { - orientation = Map::Orthogonal; - } else if (string == QLatin1String("isometric")) { - orientation = Map::Isometric; - } - return orientation; -} - Map *MapReaderPrivate::readMap() { Q_ASSERT(xml.isStartElement() && xml.name() == "map"); @@ -207,14 +197,14 @@ const int tileHeight = atts.value(QLatin1String("tileheight")).toString().toInt(); - const QStringRef orientationRef = - atts.value(QLatin1String("orientation")); + const QString orientationString = + atts.value(QLatin1String("orientation")).toString(); const Map::Orientation orientation = - orientationFromString(orientationRef); + orientationFromString(orientationString); if (orientation == Map::Unknown) { xml.raiseError(tr("Unsupported map orientation: \"%1\"") - .arg(orientationRef.toString())); + .arg(orientationString)); } mMap = new Map(orientation, mapWidth, mapHeight, tileWidth, tileHeight); @@ -276,12 +266,21 @@ tileSpacing, margin); while (xml.readNextStartElement()) { - if (xml.name() == "tile") + if (xml.name() == "tile") { readTilesetTile(tileset); - else if (xml.name() == "image") + } else if (xml.name() == "tileoffset") { + const QXmlStreamAttributes oa = xml.attributes(); + int x = oa.value(QLatin1String("x")).toString().toInt(); + int y = oa.value(QLatin1String("y")).toString().toInt(); + tileset->setTileOffset(QPoint(x, y)); + xml.skipCurrentElement(); + } else if (xml.name() == "properties") { + tileset->mergeProperties(readProperties()); + } else if (xml.name() == "image") { readTilesetImage(tileset); - else + } else { readUnknownElement(); + } } } } else { // External tileset @@ -343,6 +342,10 @@ source = p->resolveReference(source, mPath); + // Set the width that the tileset had when the map was saved + const int width = atts.value(QLatin1String("width")).toString().toInt(); + mGidMapper.setTilesetWidth(tileset, width); + const QImage tilesetImage = p->readExternalImage(source); if (!tileset->loadFromImage(tilesetImage, source)) xml.raiseError(tr("Error loading tileset image:\n'%1'").arg(source)); @@ -564,6 +567,22 @@ return objectGroup; } +static QPointF pixelToTileCoordinates(Map *map, int x, int y) +{ + const int tileHeight = map->tileHeight(); + const int tileWidth = map->tileWidth(); + + if (map->orientation() == Map::Isometric) { + // Isometric needs special handling, since the pixel values are based + // solely on the tile height. + return QPointF((qreal) x / tileHeight, + (qreal) y / tileHeight); + } else { + return QPointF((qreal) x / tileWidth, + (qreal) y / tileHeight); + } +} + MapObject *MapReaderPrivate::readObject() { Q_ASSERT(xml.isStartElement() && xml.name() == "object"); @@ -577,26 +596,11 @@ const int height = atts.value(QLatin1String("height")).toString().toInt(); const QString type = atts.value(QLatin1String("type")).toString(); - // Convert pixel coordinates to tile coordinates - const int tileHeight = mMap->tileHeight(); - const int tileWidth = mMap->tileWidth(); - qreal xF, yF, widthF, heightF; - - if (mMap->orientation() == Map::Isometric) { - // Isometric needs special handling, since the pixel values are based - // solely on the tile height. - xF = (qreal) x / tileHeight; - yF = (qreal) y / tileHeight; - widthF = (qreal) width / tileHeight; - heightF = (qreal) height / tileHeight; - } else { - xF = (qreal) x / tileWidth; - yF = (qreal) y / tileHeight; - widthF = (qreal) width / tileWidth; - heightF = (qreal) height / tileHeight; - } + const QPointF pos = pixelToTileCoordinates(mMap, x, y); + const QPointF size = pixelToTileCoordinates(mMap, width, height); - MapObject *object = new MapObject(name, type, xF, yF, widthF, heightF); + MapObject *object = new MapObject(name, type, pos, QSizeF(size.x(), + size.y())); if (gid) { const Cell cell = cellForGid(gid); @@ -604,15 +608,59 @@ } while (xml.readNextStartElement()) { - if (xml.name() == "properties") + if (xml.name() == "properties") { object->mergeProperties(readProperties()); - else + } else if (xml.name() == "polygon") { + object->setPolygon(readPolygon()); + object->setShape(MapObject::Polygon); + } else if (xml.name() == "polyline") { + object->setPolygon(readPolygon()); + object->setShape(MapObject::Polyline); + } else { readUnknownElement(); + } } return object; } +QPolygonF MapReaderPrivate::readPolygon() +{ + Q_ASSERT(xml.isStartElement() && (xml.name() == "polygon" || + xml.name() == "polyline")); + + const QXmlStreamAttributes atts = xml.attributes(); + const QString points = atts.value(QLatin1String("points")).toString(); + const QStringList pointsList = points.split(QLatin1Char(' '), + QString::SkipEmptyParts); + + QPolygonF polygon; + bool ok = true; + + foreach (const QString &point, pointsList) { + const int commaPos = point.indexOf(QLatin1Char(',')); + if (commaPos == -1) { + ok = false; + break; + } + + const int x = point.left(commaPos).toInt(&ok); + if (!ok) + break; + const int y = point.mid(commaPos + 1).toInt(&ok); + if (!ok) + break; + + polygon.append(pixelToTileCoordinates(mMap, x, y)); + } + + if (!ok) + xml.raiseError(tr("Invalid points data for polygon")); + + xml.skipCurrentElement(); + return polygon; +} + Properties MapReaderPrivate::readProperties() { Q_ASSERT(xml.isStartElement() && xml.name() == "properties"); diff -Nru tiled-qt-0.7.1/src/libtiled/maprenderer.cpp tiled-qt-0.8.0/src/libtiled/maprenderer.cpp --- tiled-qt-0.7.1/src/libtiled/maprenderer.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/maprenderer.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,46 @@ +/* + * maprenderer.cpp + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 "maprenderer.h" + +#include + +using namespace Tiled; + +/** + * Converts a line running from \a start to \a end to a polygon which + * extends 5 pixels from the line in all directions. + */ +QPolygonF MapRenderer::lineToPolygon(const QPointF &start, const QPointF &end) +{ + QPointF direction = QVector2D(end - start).normalized().toPointF(); + QPointF perpendicular(-direction.y(), direction.x()); + + const qreal thickness = 5.0f; // 5 pixels on each side + direction *= thickness; + perpendicular *= thickness; + + QPolygonF polygon(4); + polygon[0] = start + perpendicular - direction; + polygon[1] = start - perpendicular - direction; + polygon[2] = end - perpendicular + direction; + polygon[3] = end + perpendicular + direction; + return polygon; +} diff -Nru tiled-qt-0.7.1/src/libtiled/maprenderer.h tiled-qt-0.8.0/src/libtiled/maprenderer.h --- tiled-qt-0.7.1/src/libtiled/maprenderer.h 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/maprenderer.h 2011-12-11 21:49:29.000000000 +0000 @@ -1,6 +1,6 @@ /* * maprenderer.h - * Copyright 2009-2010, Thorbjørn Lindeijer + * Copyright 2009-2011, Thorbjørn Lindeijer * * This file is part of libtiled. * @@ -127,6 +127,16 @@ inline QPointF tileToPixelCoords(const QPointF &point) const { return tileToPixelCoords(point.x(), point.y()); } + QPolygonF tileToPixelCoords(const QPolygonF &polygon) const + { + QPolygonF screenPolygon(polygon.size()); + for (int i = polygon.size() - 1; i >= 0; --i) + screenPolygon[i] = tileToPixelCoords(polygon[i]); + return screenPolygon; + } + + static QPolygonF lineToPolygon(const QPointF &start, const QPointF &end); + protected: /** * Returns the map this renderer is associated with. diff -Nru tiled-qt-0.7.1/src/libtiled/mapwriter.cpp tiled-qt-0.8.0/src/libtiled/mapwriter.cpp --- tiled-qt-0.7.1/src/libtiled/mapwriter.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/mapwriter.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -157,24 +157,10 @@ { w.writeStartElement(QLatin1String("map")); - QString orientation; - switch (map->orientation()) { - case Map::Orthogonal: - orientation = QLatin1String("orthogonal"); - break; - case Map::Isometric: - orientation = QLatin1String("isometric"); - break; - case Map::Hexagonal: - orientation = QLatin1String("hexagonal"); - break; - case Map::Unknown: - break; - } + const QString orientation = orientationToString(map->orientation()); w.writeAttribute(QLatin1String("version"), QLatin1String("1.0")); - if (!orientation.isEmpty()) - w.writeAttribute(QLatin1String("orientation"), orientation); + w.writeAttribute(QLatin1String("orientation"), orientation); w.writeAttribute(QLatin1String("width"), QString::number(map->width())); w.writeAttribute(QLatin1String("height"), QString::number(map->height())); w.writeAttribute(QLatin1String("tilewidth"), @@ -234,6 +220,17 @@ if (margin != 0) w.writeAttribute(QLatin1String("margin"), QString::number(margin)); + const QPoint offset = tileset->tileOffset(); + if (!offset.isNull()) { + w.writeStartElement(QLatin1String("tileoffset")); + w.writeAttribute(QLatin1String("x"), QString::number(offset.x())); + w.writeAttribute(QLatin1String("y"), QString::number(offset.y())); + w.writeEndElement(); + } + + // Write the tileset properties + writeProperties(w, tileset->properties()); + // Write the image element const QString &imageSource = tileset->imageSource(); if (!imageSource.isEmpty()) { @@ -393,6 +390,33 @@ w.writeEndElement(); } +class TileToPixelCoordinates +{ +public: + TileToPixelCoordinates(Map *map) + { + if (map->orientation() == Map::Isometric) { + // Isometric needs special handling, since the pixel values are + // based solely on the tile height. + mMultiplierX = map->tileHeight(); + mMultiplierY = map->tileHeight(); + } else { + mMultiplierX = map->tileWidth(); + mMultiplierY = map->tileHeight(); + } + } + + QPoint operator() (qreal x, qreal y) const + { + return QPoint(qRound(x * mMultiplierX), + qRound(y * mMultiplierY)); + } + +private: + int mMultiplierX; + int mMultiplierY; +}; + void MapWriterPrivate::writeObject(QXmlStreamWriter &w, const MapObject *mapObject) { @@ -411,35 +435,41 @@ // Convert from tile to pixel coordinates const ObjectGroup *objectGroup = mapObject->objectGroup(); - const Map *map = objectGroup->map(); - const int tileHeight = map->tileHeight(); - const int tileWidth = map->tileWidth(); - const QRectF bounds = mapObject->bounds(); - - int x, y, width, height; - - if (map->orientation() == Map::Isometric) { - // Isometric needs special handling, since the pixel values are based - // solely on the tile height. - x = qRound(bounds.x() * tileHeight); - y = qRound(bounds.y() * tileHeight); - width = qRound(bounds.width() * tileHeight); - height = qRound(bounds.height() * tileHeight); - } else { - x = qRound(bounds.x() * tileWidth); - y = qRound(bounds.y() * tileHeight); - width = qRound(bounds.width() * tileWidth); - height = qRound(bounds.height() * tileHeight); - } + const TileToPixelCoordinates toPixel(objectGroup->map()); + + QPoint pos = toPixel(mapObject->x(), mapObject->y()); + QPoint size = toPixel(mapObject->width(), mapObject->height()); + + w.writeAttribute(QLatin1String("x"), QString::number(pos.x())); + w.writeAttribute(QLatin1String("y"), QString::number(pos.y())); + + if (size.x() != 0) + w.writeAttribute(QLatin1String("width"), QString::number(size.x())); + if (size.y() != 0) + w.writeAttribute(QLatin1String("height"), QString::number(size.y())); - w.writeAttribute(QLatin1String("x"), QString::number(x)); - w.writeAttribute(QLatin1String("y"), QString::number(y)); - - if (width != 0) - w.writeAttribute(QLatin1String("width"), QString::number(width)); - if (height != 0) - w.writeAttribute(QLatin1String("height"), QString::number(height)); writeProperties(w, mapObject->properties()); + + const QPolygonF &polygon = mapObject->polygon(); + if (!polygon.isEmpty()) { + if (mapObject->shape() == MapObject::Polygon) + w.writeStartElement(QLatin1String("polygon")); + else + w.writeStartElement(QLatin1String("polyline")); + + QString points; + foreach (const QPointF &point, polygon) { + const QPoint pos = toPixel(point.x(), point.y()); + points.append(QString::number(pos.x())); + points.append(QLatin1Char(',')); + points.append(QString::number(pos.y())); + points.append(QLatin1Char(' ')); + } + points.chop(1); + w.writeAttribute(QLatin1String("points"), points); + w.writeEndElement(); + } + w.writeEndElement(); } diff -Nru tiled-qt-0.7.1/src/libtiled/objectgroup.cpp tiled-qt-0.8.0/src/libtiled/objectgroup.cpp --- tiled-qt-0.7.1/src/libtiled/objectgroup.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/objectgroup.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -37,6 +37,11 @@ using namespace Tiled; +ObjectGroup::ObjectGroup() + : Layer(QString(), 0, 0, 0, 0) +{ +} + ObjectGroup::ObjectGroup(const QString &name, int x, int y, int width, int height) : Layer(name, x, y, width, height) @@ -186,7 +191,7 @@ ObjectGroup *ObjectGroup::initializeClone(ObjectGroup *clone) const { Layer::initializeClone(clone); - foreach (MapObject *object, mObjects) + foreach (const MapObject *object, mObjects) clone->addObject(object->clone()); clone->setColor(mColor); return clone; diff -Nru tiled-qt-0.7.1/src/libtiled/objectgroup.h tiled-qt-0.8.0/src/libtiled/objectgroup.h --- tiled-qt-0.7.1/src/libtiled/objectgroup.h 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/objectgroup.h 2011-12-11 21:49:29.000000000 +0000 @@ -49,7 +49,12 @@ { public: /** - * Constructor. + * Default constructor. + */ + ObjectGroup(); + + /** + * Constructor with some parameters. */ ObjectGroup(const QString &name, int x, int y, int width, int height); diff -Nru tiled-qt-0.7.1/src/libtiled/orthogonalrenderer.cpp tiled-qt-0.8.0/src/libtiled/orthogonalrenderer.cpp --- tiled-qt-0.7.1/src/libtiled/orthogonalrenderer.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/orthogonalrenderer.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -1,6 +1,6 @@ /* * orthogonalrenderer.cpp - * Copyright 2009-2010, Thorbjørn Lindeijer + * Copyright 2009-2011, Thorbjørn Lindeijer * * This file is part of libtiled. * @@ -32,6 +32,7 @@ #include "mapobject.h" #include "tile.h" #include "tilelayer.h" +#include "tileset.h" #include @@ -60,36 +61,80 @@ const QRectF rect(tileToPixelCoords(bounds.topLeft()), tileToPixelCoords(bounds.bottomRight())); - // The -2 and +3 are to account for the pen width and shadow + QRectF boundingRect; + if (object->tile()) { const QPointF bottomLeft = rect.topLeft(); const QPixmap &img = object->tile()->image(); - return QRectF(bottomLeft.x(), - bottomLeft.y() - img.height(), - img.width(), - img.height()).adjusted(-1, -1, 1, 1); - } else if (rect.isNull()) { - return rect.adjusted(-10 - 2, -10 - 2, 10 + 3, 10 + 3); + boundingRect = QRectF(bottomLeft.x(), + bottomLeft.y() - img.height(), + img.width(), + img.height()).adjusted(-1, -1, 1, 1); } else { - const int nameHeight = object->name().isEmpty() ? 0 : 15; - return rect.adjusted(-2, -nameHeight - 2, 3, 3); + // The -2 and +3 are to account for the pen width and shadow + switch (object->shape()) { + case MapObject::Rectangle: + if (rect.isNull()) { + boundingRect = rect.adjusted(-10 - 2, -10 - 2, 10 + 3, 10 + 3); + } else { + const int nameHeight = object->name().isEmpty() ? 0 : 15; + boundingRect = rect.adjusted(-2, -nameHeight - 2, 3, 3); + } + break; + + case MapObject::Polygon: + case MapObject::Polyline: { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + QPolygonF screenPolygon = tileToPixelCoords(polygon); + boundingRect = screenPolygon.boundingRect().adjusted(-2, -2, 3, 3); + break; + } + } } + + return boundingRect; } QPainterPath OrthogonalRenderer::shape(const MapObject *object) const { - const QRectF bounds = object->bounds(); - const QRectF rect(tileToPixelCoords(bounds.topLeft()), - tileToPixelCoords(bounds.bottomRight())); - QPainterPath path; + if (object->tile()) { path.addRect(boundingRect(object)); - } else if (rect.isNull()) { - path.addEllipse(rect.topLeft(), 20, 20); } else { - path.addRoundedRect(rect, 10, 10); + switch (object->shape()) { + case MapObject::Rectangle: { + const QRectF bounds = object->bounds(); + const QRectF rect(tileToPixelCoords(bounds.topLeft()), + tileToPixelCoords(bounds.bottomRight())); + + if (rect.isNull()) { + path.addEllipse(rect.topLeft(), 20, 20); + } else { + path.addRoundedRect(rect, 10, 10); + } + break; + } + case MapObject::Polygon: + case MapObject::Polyline: { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + const QPolygonF screenPolygon = tileToPixelCoords(polygon); + if (object->shape() == MapObject::Polygon) { + path.addPolygon(screenPolygon); + } else { + for (int i = 1; i < screenPolygon.size(); ++i) { + path.addPolygon(lineToPolygon(screenPolygon[i - 1], + screenPolygon[i])); + } + path.setFillRule(Qt::WindingFill); + } + break; + } + } } + return path; } @@ -133,6 +178,8 @@ const TileLayer *layer, const QRectF &exposed) const { + QTransform savedTransform = painter->transform(); + const int tileWidth = map()->tileWidth(); const int tileHeight = map()->tileHeight(); const QPointF layerPos(layer->x() * tileWidth, @@ -146,10 +193,15 @@ int endY = layer->height(); if (!exposed.isNull()) { - const QSize maxTileSize = layer->maxTileSize(); - const int extraWidth = maxTileSize.width() - tileWidth; - const int extraHeight = maxTileSize.height() - tileHeight; - QRectF rect = exposed.adjusted(-extraWidth, 0, 0, extraHeight); + QMargins drawMargins = layer->drawMargins(); + drawMargins.setTop(drawMargins.top() - tileHeight); + drawMargins.setRight(drawMargins.right() - tileWidth); + + QRectF rect = exposed.adjusted(-drawMargins.right(), + -drawMargins.bottom(), + drawMargins.left(), + drawMargins.top()); + rect.translate(-layerPos); startX = qMax((int) rect.x() / tileWidth, 0); @@ -158,6 +210,8 @@ endY = qMin((int) std::ceil(rect.bottom()) / tileHeight + 1, endY); } + QTransform baseTransform = painter->transform(); + for (int y = startY; y < endY; ++y) { for (int x = startX; x < endX; ++x) { const Cell &cell = layer->cellAt(x, y); @@ -165,20 +219,44 @@ continue; const QPixmap &img = cell.tile->image(); - const int flipX = cell.flippedHorizontally ? -1 : 1; - const int flipY = cell.flippedVertically ? -1 : 1; - const int offsetX = cell.flippedHorizontally ? img.width() : 0; - const int offsetY = cell.flippedVertically ? 0 : img.height(); - - painter->scale(flipX, flipY); - painter->drawPixmap(x * tileWidth * flipX - offsetX, - (y + 1) * tileHeight * flipY - offsetY, - img); - painter->scale(flipX, flipY); + const QPoint offset = cell.tile->tileset()->tileOffset(); + + qreal m11 = 1; // Horizontal scaling factor + qreal m12 = 0; // Vertical shearing factor + qreal m21 = 0; // Horizontal shearing factor + qreal m22 = 1; // Vertical scaling factor + qreal dx = offset.x() + x * tileWidth; + qreal dy = offset.y() + (y + 1) * tileHeight - img.height(); + + if (cell.flippedAntiDiagonally) { + // Use shearing to swap the X/Y axis + m11 = 0; + m12 = 1; + m21 = 1; + m22 = 0; + + // Compensate for the swap of image dimensions + dy += img.height() - img.width(); + } + if (cell.flippedHorizontally) { + m11 = -m11; + m21 = -m21; + dx += cell.flippedAntiDiagonally ? img.height() : img.width(); + } + if (cell.flippedVertically) { + m12 = -m12; + m22 = -m22; + dy += cell.flippedAntiDiagonally ? img.width() : img.height(); + } + + const QTransform transform(m11, m12, m21, m22, dx, dy); + painter->setTransform(transform * baseTransform); + + painter->drawPixmap(0, 0, img); } } - painter->translate(-layerPos); + painter->setTransform(savedTransform); } void OrthogonalRenderer::drawTileSelection(QPainter *painter, @@ -206,8 +284,7 @@ painter->translate(rect.topLeft()); rect.moveTopLeft(QPointF(0, 0)); - if (object->tile()) - { + if (object->tile()) { const QPixmap &img = object->tile()->image(); const QPoint paintOrigin(0, -img.height()); painter->drawPixmap(paintOrigin, img); @@ -219,35 +296,64 @@ pen.setColor(color); painter->setPen(pen); painter->drawRect(QRect(paintOrigin, img.size())); - } - else - { - if (rect.isNull()) - rect = QRectF(QPointF(-10, -10), QSizeF(20, 20)); - - const QFontMetrics fm = painter->fontMetrics(); - QString name = fm.elidedText(object->name(), Qt::ElideRight, - rect.width() + 2); + } else { + const QPen linePen(color, 2); + const QPen shadowPen(Qt::black, 2); + + QColor brushColor = color; + brushColor.setAlpha(50); + const QBrush fillBrush(brushColor); painter->setRenderHint(QPainter::Antialiasing); - // Draw the shadow - QPen pen(Qt::black, 2); - painter->setPen(pen); - painter->drawRect(rect.translated(QPointF(1, 1))); - if (!name.isEmpty()) - painter->drawText(QPoint(1, -5 + 1), name); + switch (object->shape()) { + case MapObject::Rectangle: { + if (rect.isNull()) + rect = QRectF(QPointF(-10, -10), QSizeF(20, 20)); + + const QFontMetrics fm = painter->fontMetrics(); + QString name = fm.elidedText(object->name(), Qt::ElideRight, + rect.width() + 2); + + // Draw the shadow + painter->setPen(shadowPen); + painter->drawRect(rect.translated(QPointF(1, 1))); + if (!name.isEmpty()) + painter->drawText(QPoint(1, -5 + 1), name); + + painter->setPen(linePen); + painter->setBrush(fillBrush); + painter->drawRect(rect); + if (!name.isEmpty()) + painter->drawText(QPoint(0, -5), name); - QColor brushColor = color; - brushColor.setAlpha(50); - QBrush brush(brushColor); + break; + } - pen.setColor(color); - painter->setPen(pen); - painter->setBrush(brush); - painter->drawRect(rect); - if (!name.isEmpty()) - painter->drawText(QPoint(0, -5), name); + case MapObject::Polyline: { + QPolygonF screenPolygon = tileToPixelCoords(object->polygon()); + + painter->setPen(shadowPen); + painter->drawPolyline(screenPolygon.translated(1, 1)); + + painter->setPen(linePen); + painter->setBrush(fillBrush); + painter->drawPolyline(screenPolygon); + break; + } + + case MapObject::Polygon: { + QPolygonF screenPolygon = tileToPixelCoords(object->polygon()); + + painter->setPen(shadowPen); + painter->drawPolygon(screenPolygon.translated(1, 1)); + + painter->setPen(linePen); + painter->setBrush(fillBrush); + painter->drawPolygon(screenPolygon); + break; + } + } } painter->restore(); diff -Nru tiled-qt-0.7.1/src/libtiled/tilelayer.cpp tiled-qt-0.8.0/src/libtiled/tilelayer.cpp --- tiled-qt-0.7.1/src/libtiled/tilelayer.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/tilelayer.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -67,21 +67,44 @@ return region; } +static QSize maxSize(const QSize &a, + const QSize &b) +{ + return QSize(qMax(a.width(), b.width()), + qMax(a.height(), b.height())); +} + +static QMargins maxMargins(const QMargins &a, + const QMargins &b) +{ + return QMargins(qMax(a.left(), b.left()), + qMax(a.top(), b.top()), + qMax(a.right(), b.right()), + qMax(a.bottom(), b.bottom())); +} + void TileLayer::setCell(int x, int y, const Cell &cell) { Q_ASSERT(contains(x, y)); if (cell.tile) { - if (cell.tile->width() > mMaxTileSize.width()) { - mMaxTileSize.setWidth(cell.tile->width()); - if (mMap) - mMap->adjustMaxTileSize(mMaxTileSize); - } - if (cell.tile->height() > mMaxTileSize.height()) { - mMaxTileSize.setHeight(cell.tile->height()); - if (mMap) - mMap->adjustMaxTileSize(mMaxTileSize); - } + int width = cell.tile->width(); + int height = cell.tile->height(); + + if (cell.flippedAntiDiagonally) + std::swap(width, height); + + const QPoint offset = cell.tile->tileset()->tileOffset(); + + mMaxTileSize = maxSize(QSize(width, height), mMaxTileSize); + mOffsetMargins = maxMargins(QMargins(-offset.x(), + -offset.y(), + offset.x(), + offset.y()), + mOffsetMargins); + + if (mMap) + mMap->adjustDrawMargins(drawMargins()); } mGrid[x + y * mWidth] = cell; @@ -145,6 +168,8 @@ { QVector newGrid(mWidth * mHeight); + Q_ASSERT(direction == FlipHorizontally || direction == FlipVertically); + for (int y = 0; y < mHeight; ++y) { for (int x = 0; x < mWidth; ++x) { Cell &dest = newGrid[x + y * mWidth]; @@ -152,17 +177,61 @@ const Cell &source = cellAt(mWidth - x - 1, y); dest = source; dest.flippedHorizontally = !source.flippedHorizontally; - } else { + } else if (direction == FlipVertically) { const Cell &source = cellAt(x, mHeight - y - 1); dest = source; dest.flippedVertically = !source.flippedVertically; - } + } } } mGrid = newGrid; } +void TileLayer::rotate(RotateDirection direction) +{ + static const char rotateRightMask[8] = { 5, 4, 1, 0, 7, 6, 3, 2 }; + static const char rotateLeftMask[8] = { 3, 2, 7, 6, 1, 0, 5, 4 }; + + const char (&rotateMask)[8] = + (direction == RotateRight) ? rotateRightMask : rotateLeftMask; + + int newWidth = mHeight; + int newHeight = mWidth; + QVector newGrid(newWidth * newHeight); + + for (int y = 0; y < mHeight; ++y) { + for (int x = 0; x < mWidth; ++x) { + const Cell &source = cellAt(x, y); + Cell dest = source; + + unsigned char mask = + (dest.flippedHorizontally << 2) | + (dest.flippedVertically << 1) | + (dest.flippedAntiDiagonally << 0); + + mask = rotateMask[mask]; + + dest.flippedHorizontally = (mask & 4) != 0; + dest.flippedVertically = (mask & 2) != 0; + dest.flippedAntiDiagonally = (mask & 1) != 0; + + if (direction == RotateRight) + newGrid[x * newWidth + (mHeight - y - 1)] = dest; + else + newGrid[(mWidth - x - 1) * newWidth + y] = dest; + } + } + + std::swap(mMaxTileSize.rwidth(), + mMaxTileSize.rheight()); + + mWidth = newWidth; + mHeight = newHeight; + mGrid = newGrid; +} + + QSet TileLayer::usedTilesets() const { QSet tilesets; @@ -351,5 +420,6 @@ Layer::initializeClone(clone); clone->mGrid = mGrid; clone->mMaxTileSize = mMaxTileSize; + clone->mOffsetMargins = mOffsetMargins; return clone; } diff -Nru tiled-qt-0.7.1/src/libtiled/tilelayer.h tiled-qt-0.8.0/src/libtiled/tilelayer.h --- tiled-qt-0.7.1/src/libtiled/tilelayer.h 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/tilelayer.h 2011-12-11 21:49:29.000000000 +0000 @@ -34,6 +34,7 @@ #include "layer.h" +#include #include #include @@ -51,13 +52,15 @@ Cell() : tile(0), flippedHorizontally(false), - flippedVertically(false) + flippedVertically(false), + flippedAntiDiagonally(false) {} explicit Cell(Tile *tile) : tile(tile), flippedHorizontally(false), - flippedVertically(false) + flippedVertically(false), + flippedAntiDiagonally(false) {} bool isEmpty() const { return tile == 0; } @@ -66,19 +69,22 @@ { return tile == other.tile && flippedHorizontally == other.flippedHorizontally - && flippedVertically == other.flippedVertically; + && flippedVertically == other.flippedVertically + && flippedAntiDiagonally == other.flippedAntiDiagonally; } bool operator != (const Cell &other) const { return tile != other.tile || flippedHorizontally != other.flippedHorizontally - || flippedVertically != other.flippedVertically; + || flippedVertically != other.flippedVertically + || flippedAntiDiagonally != other.flippedAntiDiagonally; } Tile *tile; bool flippedHorizontally; bool flippedVertically; + bool flippedAntiDiagonally; }; /** @@ -93,7 +99,13 @@ public: enum FlipDirection { FlipHorizontally, - FlipVertically + FlipVertically, + FlipDiagonally + }; + + enum RotateDirection { + RotateLeft, + RotateRight }; /** @@ -102,12 +114,25 @@ TileLayer(const QString &name, int x, int y, int width, int height); /** - * Returns the maximum tile size of this layer. Used by the layer - * rendering code to determine the area that needs to be redrawn. + * Returns the maximum tile size of this layer. */ QSize maxTileSize() const { return mMaxTileSize; } /** + * Returns the margins that have to be taken into account while drawing + * this tile layer. The margins depend on the maximum tile size and the + * offset applied to the tiles. + */ + QMargins drawMargins() const + { + return QMargins(mOffsetMargins.left(), + mOffsetMargins.top() + mMaxTileSize.height(), + mOffsetMargins.right() + mMaxTileSize.width(), + mOffsetMargins.bottom()); + } + + + /** * Returns whether (x, y) is inside this map layer. */ bool contains(int x, int y) const @@ -164,12 +189,20 @@ const QRegion &mask = QRegion()); /** - * Flip this tile layer in the given \a direction. This doesn't change the - * dimensions of the tile layer. + * Flip this tile layer in the given \a direction. Direction must be + * horizontal or vertical. This doesn't change the dimensions of the + * tile layer. */ void flip(FlipDirection direction); /** + * Rotate this tile layer by 90 degrees left or right. The tile positions + * are rotated within the layer, and the tiles themselves are rotated. The + * dimensions of the tile layer are swapped. + */ + void rotate(RotateDirection direction); + + /** * Computes and returns the set of tilesets used by this tile layer. */ QSet usedTilesets() const; @@ -237,6 +270,7 @@ private: QSize mMaxTileSize; + QMargins mOffsetMargins; QVector mGrid; }; diff -Nru tiled-qt-0.7.1/src/libtiled/tileset.cpp tiled-qt-0.8.0/src/libtiled/tileset.cpp --- tiled-qt-0.7.1/src/libtiled/tileset.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/tileset.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -87,8 +87,7 @@ mImageWidth = image.width(); mImageHeight = image.height(); - mColumnCount = (image.width() - mMargin * 2 + mTileSpacing) - / (mTileWidth + mTileSpacing); + mColumnCount = columnCountForWidth(mImageWidth); mImageSource = fileName; return true; } @@ -107,3 +106,9 @@ } return 0; } + +int Tileset::columnCountForWidth(int width) const +{ + Q_ASSERT(mTileWidth > 0); + return (width - mMargin + mTileSpacing) / (mTileWidth + mTileSpacing); +} diff -Nru tiled-qt-0.7.1/src/libtiled/tileset.h tiled-qt-0.8.0/src/libtiled/tileset.h --- tiled-qt-0.7.1/src/libtiled/tileset.h 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/libtiled/tileset.h 2011-12-11 21:49:29.000000000 +0000 @@ -30,10 +30,11 @@ #ifndef TILESET_H #define TILESET_H -#include "tiled_global.h" +#include "object.h" #include #include +#include #include class QImage; @@ -48,7 +49,7 @@ * This class currently only supports loading tiles from a tileset image, using * loadFromImage(). There is no way to add or remove arbitrary tiles. */ -class TILEDSHARED_EXPORT Tileset +class TILEDSHARED_EXPORT Tileset : public Object { public: /** @@ -71,6 +72,8 @@ mImageHeight(0), mColumnCount(0) { + Q_ASSERT(tileSpacing >= 0); + Q_ASSERT(margin >= 0); } /** @@ -125,6 +128,17 @@ int margin() const { return mMargin; } /** + * Returns the offset that is applied when drawing the tiles in this + * tileset. + */ + QPoint tileOffset() const { return mTileOffset; } + + /** + * @see tileOffset + */ + void setTileOffset(QPoint offset) { mTileOffset = offset; } + + /** * Returns the tile for the given tile ID. * The tile ID is local to this tileset, which means the IDs are in range * [0, tileCount() - 1]. @@ -192,6 +206,13 @@ */ const QString &imageSource() const { return mImageSource; } + /** + * Returns the column count that this tileset would have if the tileset + * image would have the given \a width. This takes into account the tile + * size, margin and spacing. + */ + int columnCountForWidth(int width) const; + private: QString mName; QString mFileName; @@ -201,6 +222,7 @@ int mTileHeight; int mTileSpacing; int mMargin; + QPoint mTileOffset; int mImageWidth; int mImageHeight; int mColumnCount; diff -Nru tiled-qt-0.7.1/src/plugins/flare/flare_global.h tiled-qt-0.8.0/src/plugins/flare/flare_global.h --- tiled-qt-0.7.1/src/plugins/flare/flare_global.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/flare/flare_global.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,33 @@ +/* + * Flare Tiled Plugin + * Copyright 2010, Jaderamiso + * Copyright 2011, Stefan Beller + * + * 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 . + */ + +#ifndef FLARE_GLOBAL_H +#define FLARE_GLOBAL_H + +#include + +#if defined(FLARE_LIBRARY) +# define FLARESHARED_EXPORT Q_DECL_EXPORT +#else +# define FLARESHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // FLARE_GLOBAL_H diff -Nru tiled-qt-0.7.1/src/plugins/flare/flareplugin.cpp tiled-qt-0.8.0/src/plugins/flare/flareplugin.cpp --- tiled-qt-0.7.1/src/plugins/flare/flareplugin.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/flare/flareplugin.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,160 @@ +/* + * Flare Tiled Plugin + * Copyright 2010, Jaderamiso + * Copyright 2011, Stefan Beller + * Copyright 2011, Clint Bellanger + * + * 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 "flareplugin.h" + +#include "gidmapper.h" +#include "map.h" +#include "mapobject.h" +#include "tile.h" +#include "tilelayer.h" +#include "tileset.h" +#include "objectgroup.h" + +#include +#include + +using namespace Flare; +using namespace Tiled; + +FlarePlugin::FlarePlugin() +{ +} + +QString FlarePlugin::nameFilter() const +{ + return tr("Flare map files (*.txt)"); +} + +QString FlarePlugin::errorString() const +{ + return mError; +} + +bool FlarePlugin::write(const Tiled::Map *map, const QString &fileName) +{ + if (!checkOneLayerWithName(map, QLatin1String("background"))) + return false; + if (!checkOneLayerWithName(map, QLatin1String("object"))) + return false; + if (!checkOneLayerWithName(map, QLatin1String("collision"))) + return false; + + QFile file(fileName); + if (!file.open(QFile::WriteOnly | QFile::Text)) { + mError = tr("Could not open file for writing."); + return false; + } + + QTextStream out(&file); + out.setCodec("UTF-8"); + + const int mapWidth = map->width(); + const int mapHeight = map->height(); + + // write [header] + out << "[header]\n"; + out << "width=" << mapWidth << "\n"; + out << "height=" << mapHeight << "\n"; + + // write all properties for this map + Properties::const_iterator it = map->properties().constBegin(); + Properties::const_iterator it_end = map->properties().constEnd(); + for (; it != it_end; ++it) { + out << it.key() << "=" << it.value() << "\n"; + } + out << "\n"; + + + GidMapper gidMapper(map->tilesets()); + + // write layers + foreach (Layer *layer, map->layers()) { + if (TileLayer *tileLayer = layer->asTileLayer()) { + out << "[layer]\n"; + out << "type=" << layer->name() << "\n"; + out << "data=\n"; + for (int y = 0; y < mapHeight; ++y) { + for (int x = 0; x < mapWidth; ++x) { + Cell t = tileLayer->cellAt(x, y); + int id = 0; + if (t.tile) + id = gidMapper.cellToGid(t); + out << id; + if (x < mapWidth - 1) + out << ","; + } + if (y < mapHeight - 1) + out << ","; + out << "\n"; + } + out << "\n"; + } + if (ObjectGroup *group = layer->asObjectGroup()) { + foreach (const MapObject *o, group->objects()) { + if (o->type().isEmpty()) { + out << "[" << group->name() << "]\n"; + + // display object name as comment + if (o->name().isEmpty()) { + out << "# " << o->name() << "\n"; + } + + out << "type=" << o->type() << "\n"; + out << "location=" << o->x() << "," << o->y(); + out << "," << o->width() << "," << o->height() << "\n"; + + // write all properties for this object + Properties::const_iterator it = o->properties().constBegin(); + Properties::const_iterator it_end = o->properties().constEnd(); + for (; it != it_end; ++it) { + out << it.key() << "=" << it.value() << "\n"; + } + out << "\n"; + } + } + } + } + file.close(); + return true; +} + +bool FlarePlugin::checkOneLayerWithName(const Tiled::Map *map, + const QString &name) +{ + int count = 0; + foreach (const Layer *layer, map->layers()) + if (layer->name() == name) + count++; + + if (count == 0) { + mError = tr("No \"%1\" layer found!").arg(name); + return false; + } else if (count > 1) { + mError = tr("Multiple \"%1\" layers found!").arg(name); + return false; + } + + return true; +} + +Q_EXPORT_PLUGIN2(Flare, FlarePlugin) diff -Nru tiled-qt-0.7.1/src/plugins/flare/flareplugin.h tiled-qt-0.8.0/src/plugins/flare/flareplugin.h --- tiled-qt-0.7.1/src/plugins/flare/flareplugin.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/flare/flareplugin.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,57 @@ +/* + * Flare Tiled Plugin + * Copyright 2010, Jaderamiso + * Copyright 2011, Stefan Beller + * + * 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 . + */ + +#ifndef FLAREPLUGIN_H +#define FLAREPLUGIN_H + +#include "flare_global.h" + +#include "mapwriterinterface.h" + +#include +#include + +namespace Flare { + +class FLARESHARED_EXPORT FlarePlugin + : public QObject + , public Tiled::MapWriterInterface +{ + Q_OBJECT + Q_INTERFACES(Tiled::MapWriterInterface) + +public: + FlarePlugin(); + + // MapWriterInterface + bool write(const Tiled::Map *map, const QString &fileName); + QString nameFilter() const; + QString errorString() const; + +private: + bool checkOneLayerWithName(const Tiled::Map *map, const QString &name); + + QString mError; +}; + +} // namespace Flare + +#endif // FLAREPLUGIN_H diff -Nru tiled-qt-0.7.1/src/plugins/flare/flare.pro tiled-qt-0.8.0/src/plugins/flare/flare.pro --- tiled-qt-0.7.1/src/plugins/flare/flare.pro 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/flare/flare.pro 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,9 @@ +include(../plugin.pri) + +DEFINES += FLARE_LIBRARY +HEADERS += \ + flare_global.h \ + flareplugin.h + +SOURCES += \ + flareplugin.cpp diff -Nru tiled-qt-0.7.1/src/plugins/json/json_global.h tiled-qt-0.8.0/src/plugins/json/json_global.h --- tiled-qt-0.7.1/src/plugins/json/json_global.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/json_global.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,33 @@ +/* + * JSON Tiled Plugin + * Copyright 2011, Porfírio José Pereira Ribeiro + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 . + */ + +#ifndef JSON_GLOBAL_H +#define JSON_GLOBAL_H + +#include + +#if defined(JSON_LIBRARY) +# define JSONSHARED_EXPORT Q_DECL_EXPORT +#else +# define JSONSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // JSON_GLOBAL_H diff -Nru tiled-qt-0.7.1/src/plugins/json/jsonplugin.cpp tiled-qt-0.8.0/src/plugins/json/jsonplugin.cpp --- tiled-qt-0.7.1/src/plugins/json/jsonplugin.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/jsonplugin.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,113 @@ +/* + * JSON Tiled Plugin + * Copyright 2011, Porfírio José Pereira Ribeiro + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 "jsonplugin.h" + +#include "maptovariantconverter.h" +#include "varianttomapconverter.h" + +#include "qjsonparser/json.h" + +#include +#include +#include + +using namespace Json; + +JsonPlugin::JsonPlugin() +{ +} + +Tiled::Map *JsonPlugin::read(const QString &fileName) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + mError = tr("Could not open file for reading."); + return false; + } + + JsonReader reader; + reader.parse(file.readAll()); + + const QVariant variant = reader.result(); + + if (!variant.isValid()) { + mError = tr("Error parsing file."); + return 0; + } + + VariantToMapConverter converter; + Tiled::Map *map = converter.toMap(variant, QFileInfo(fileName).dir()); + + if (!map) + mError = converter.errorString(); + + return map; +} + +bool JsonPlugin::write(const Tiled::Map *map, const QString &fileName) +{ + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)){ + mError = tr("Could not open file for writing."); + return false; + } + + MapToVariantConverter converter; + QVariant variant = converter.toVariant(map, QFileInfo(fileName).dir()); + + JsonWriter writer; + writer.setAutoFormatting(true); + + if (!writer.stringify(variant)) { + // This can only happen due to coding error + mError = writer.errorString(); + return false; + } + + QTextStream out(&file); + out << writer.result(); + out.flush(); + + if (file.error() != QFile::NoError) { + mError = tr("Error while writing file:\n%1").arg(file.errorString()); + return false; + } + + return true; +} + +QString JsonPlugin::nameFilter() const +{ + return tr("Json files (*.json)"); +} + +bool JsonPlugin::supportsFile(const QString &fileName) const +{ + return fileName.endsWith(QLatin1String(".json"), Qt::CaseInsensitive); +} + +QString JsonPlugin::errorString() const +{ + return mError; +} + +Q_EXPORT_PLUGIN2(Json, JsonPlugin) diff -Nru tiled-qt-0.7.1/src/plugins/json/jsonplugin.h tiled-qt-0.8.0/src/plugins/json/jsonplugin.h --- tiled-qt-0.7.1/src/plugins/json/jsonplugin.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/jsonplugin.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,66 @@ +/* + * JSON Tiled Plugin + * Copyright 2011, Porfírio José Pereira Ribeiro + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 . + */ + +#ifndef JSONPLUGIN_H +#define JSONPLUGIN_H + +#include "json_global.h" + +#include "mapwriterinterface.h" +#include "mapreaderinterface.h" + +#include + +namespace Tiled { +class Map; +} + +namespace Json { + +class JSONSHARED_EXPORT JsonPlugin + : public QObject + , public Tiled::MapReaderInterface + , public Tiled::MapWriterInterface +{ + Q_OBJECT + Q_INTERFACES(Tiled::MapReaderInterface Tiled::MapWriterInterface) + +public: + JsonPlugin(); + + // MapReaderInterface + Tiled::Map *read(const QString &fileName); + bool supportsFile(const QString &fileName) const; + + // MapWriterInterface + bool write(const Tiled::Map *map, const QString &fileName); + + // Both interfaces + QString nameFilter() const; + QString errorString() const; + +private: + QString mError; +}; + +} // namespace Json + +#endif // JSONPLUGIN_H diff -Nru tiled-qt-0.7.1/src/plugins/json/json.pro tiled-qt-0.8.0/src/plugins/json/json.pro --- tiled-qt-0.7.1/src/plugins/json/json.pro 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/json.pro 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,14 @@ +include(../plugin.pri) + +DEFINES += JSON_LIBRARY + +SOURCES += jsonplugin.cpp \ + qjsonparser/json.cpp \ + varianttomapconverter.cpp \ + maptovariantconverter.cpp + +HEADERS += jsonplugin.h \ + json_global.h \ + qjsonparser/json.h \ + varianttomapconverter.h \ + maptovariantconverter.h diff -Nru tiled-qt-0.7.1/src/plugins/json/maptovariantconverter.cpp tiled-qt-0.8.0/src/plugins/json/maptovariantconverter.cpp --- tiled-qt-0.7.1/src/plugins/json/maptovariantconverter.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/maptovariantconverter.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,248 @@ +/* + * JSON Tiled Plugin + * Copyright 2011, Porfírio José Pereira Ribeiro + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 "maptovariantconverter.h" + +#include "map.h" +#include "mapobject.h" +#include "objectgroup.h" +#include "properties.h" +#include "tile.h" +#include "tilelayer.h" +#include "tileset.h" + +using namespace Tiled; +using namespace Json; + +QVariant MapToVariantConverter::toVariant(const Map *map, const QDir &mapDir) +{ + mMapDir = mapDir; + mGidMapper.clear(); + + QVariantMap mapVariant; + + mapVariant["version"] = 1.0; + mapVariant["orientation"] = orientationToString(map->orientation()); + mapVariant["width"] = map->width(); + mapVariant["height"] = map->height(); + mapVariant["tilewidth"] = map->tileWidth(); + mapVariant["tileheight"] = map->tileHeight(); + mapVariant["properties"] = toVariant(map->properties()); + + QVariantList tilesetVariants; + + uint firstGid = 1; + foreach (Tileset *tileset, map->tilesets()) { + tilesetVariants << toVariant(tileset, firstGid); + mGidMapper.insert(firstGid, tileset); + firstGid += tileset->tileCount(); + } + mapVariant["tilesets"] = tilesetVariants; + + QVariantList layerVariants; + foreach (const Layer *layer, map->layers()) { + const TileLayer *tileLayer = dynamic_cast(layer); + const ObjectGroup *objectGroup = dynamic_cast(layer); + if (tileLayer != 0) + layerVariants << toVariant(tileLayer); + else if (objectGroup != 0) + layerVariants << toVariant(objectGroup); + } + mapVariant["layers"] = layerVariants; + + return mapVariant; +} + +QVariant MapToVariantConverter::toVariant(const Tileset *tileset, int firstGid) +{ + QVariantMap tilesetVariant; + + tilesetVariant["firstgid"] = firstGid; + tilesetVariant["name"] = tileset->name(); + tilesetVariant["tilewidth"] = tileset->tileWidth(); + tilesetVariant["tileheight"] = tileset->tileHeight(); + tilesetVariant["spacing"] = tileset->tileSpacing(); + tilesetVariant["margin"] = tileset->margin(); + tilesetVariant["properties"] = toVariant(tileset->properties()); + + // Write the image element + const QString &imageSource = tileset->imageSource(); + if (!imageSource.isEmpty()) { + const QString rel = mMapDir.relativeFilePath(tileset->imageSource()); + + tilesetVariant["image"] = rel; + + const QColor transColor = tileset->transparentColor(); + if (transColor.isValid()) + tilesetVariant["transparentcolor"] = transColor.name(); + + tilesetVariant["imagewidth"] = tileset->imageWidth(); + tilesetVariant["imageheight"] = tileset->imageHeight(); + } + + // Write the properties for those tiles that have them + QVariantMap tilePropertiesVariant; + for (int i = 0; i < tileset->tileCount(); ++i) { + const Tile *tile = tileset->tileAt(i); + const Properties properties = tile->properties(); + if (!properties.isEmpty()) + tilePropertiesVariant[QString::number(i)] = toVariant(properties); + } + if (!tilePropertiesVariant.empty()) + tilesetVariant["tileproperties"] = tilePropertiesVariant; + + return tilesetVariant; +} + +QVariant MapToVariantConverter::toVariant(const Properties &properties) +{ + QVariantMap variantMap; + + Properties::const_iterator it = properties.constBegin(); + Properties::const_iterator it_end = properties.constEnd(); + for (; it != it_end; ++it) + variantMap[it.key()] = it.value(); + + return variantMap; +} + +QVariant MapToVariantConverter::toVariant(const TileLayer *tileLayer) +{ + QVariantMap tileLayerVariant; + tileLayerVariant["type"] = "tilelayer"; + + addLayerAttributes(tileLayerVariant, tileLayer); + + QVariantList tileVariants; + for (int y = 0; y < tileLayer->height(); ++y) + for (int x = 0; x < tileLayer->width(); ++x) + tileVariants << mGidMapper.cellToGid(tileLayer->cellAt(x, y)); + + tileLayerVariant["data"] = tileVariants; + return tileLayerVariant; +} + +// TODO: Unduplicate this class since it's used also in mapwriter.cpp +class TileToPixelCoordinates +{ +public: + TileToPixelCoordinates(Map *map) + { + if (map->orientation() == Map::Isometric) { + // Isometric needs special handling, since the pixel values are + // based solely on the tile height. + mMultiplierX = map->tileHeight(); + mMultiplierY = map->tileHeight(); + } else { + mMultiplierX = map->tileWidth(); + mMultiplierY = map->tileHeight(); + } + } + + QPoint operator() (qreal x, qreal y) const + { + return QPoint(qRound(x * mMultiplierX), + qRound(y * mMultiplierY)); + } + +private: + int mMultiplierX; + int mMultiplierY; +}; + +QVariant MapToVariantConverter::toVariant(const ObjectGroup *objectGroup) +{ + QVariantMap objectGroupVariant; + objectGroupVariant["type"] = "objectgroup"; + + if (objectGroup->color().isValid()) + objectGroupVariant["color"] = objectGroup->color().name(); + + addLayerAttributes(objectGroupVariant, objectGroup); + QVariantList objectVariants; + foreach (const MapObject *object, objectGroup->objects()) { + QVariantMap objectVariant; + const QString &name = object->name(); + const QString &type = object->type(); + + objectVariant["properties"] = toVariant(object->properties()); + objectVariant["name"] = name; + objectVariant["type"] = type; + if (object->tile()) + objectVariant["gid"] = mGidMapper.cellToGid(Cell(object->tile())); + + const TileToPixelCoordinates toPixel(objectGroup->map()); + + const QPoint pos = toPixel(object->x(), object->y()); + const QPoint size = toPixel(object->width(), object->height()); + + objectVariant["x"] = pos.x(); + objectVariant["y"] = pos.y(); + objectVariant["width"] = size.x(); + objectVariant["height"] = size.y(); + + /* Polygons are stored in this format: + * + * "polygon/polyline": [ + * { "x": 0, "y": 0 }, + * { "x": 1, "y": 1 }, + * ... + * ] + */ + const QPolygonF &polygon = object->polygon(); + if (!polygon.isEmpty()) { + QVariantList pointVariants; + foreach (const QPointF &point, polygon) { + const QPoint pixelCoordinates = toPixel(point.x(), point.y()); + QVariantMap pointVariant; + pointVariant["x"] = pixelCoordinates.x(); + pointVariant["y"] = pixelCoordinates.y(); + pointVariants.append(pointVariant); + } + + if (object->shape() == MapObject::Polygon) + objectVariant["polygon"] = pointVariants; + else + objectVariant["polyline"] = pointVariants; + } + + objectVariants << objectVariant; + } + + objectGroupVariant["objects"] = objectVariants; + return objectGroupVariant; +} + +void MapToVariantConverter::addLayerAttributes(QVariantMap &layerVariant, + const Layer *layer) +{ + layerVariant["name"] = layer->name(); + layerVariant["width"] = layer->width(); + layerVariant["height"] = layer->height(); + layerVariant["x"] = layer->x(); + layerVariant["y"] = layer->y(); + layerVariant["visible"] = layer->isVisible(); + layerVariant["opacity"] = layer->opacity(); + + const Properties &properties = layer->properties(); + if (!properties.isEmpty()) + layerVariant["properties"] = toVariant(properties); +} diff -Nru tiled-qt-0.7.1/src/plugins/json/maptovariantconverter.h tiled-qt-0.8.0/src/plugins/json/maptovariantconverter.h --- tiled-qt-0.7.1/src/plugins/json/maptovariantconverter.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/maptovariantconverter.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,65 @@ +/* + * JSON Tiled Plugin + * Copyright 2011, Porfírio José Pereira Ribeiro + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 . + */ + +#ifndef MAPTOVARIANTCONVERTER_H +#define MAPTOVARIANTCONVERTER_H + +#include +#include + +#include "gidmapper.h" + +namespace Tiled { +} + +namespace Json { + +/** + * Converts Map instances to QVariant. Meant to be used together with + * JsonWriter. + */ +class MapToVariantConverter +{ +public: + MapToVariantConverter() {} + + /** + * Converts the given \s map to a QVariant. The \a mapDir is used to + * construct relative paths to external resources. + */ + QVariant toVariant(const Tiled::Map *map, const QDir &mapDir); + +private: + QVariant toVariant(const Tiled::Tileset *tileset, int firstGid); + QVariant toVariant(const Tiled::Properties &properties); + QVariant toVariant(const Tiled::TileLayer *tileLayer); + QVariant toVariant(const Tiled::ObjectGroup *objectGroup); + + void addLayerAttributes(QVariantMap &layerVariant, + const Tiled::Layer *layer); + + QDir mMapDir; + Tiled::GidMapper mGidMapper; +}; + +} // namespace Json + +#endif // MAPTOVARIANTCONVERTER_H diff -Nru tiled-qt-0.7.1/src/plugins/json/qjsonparser/json.cpp tiled-qt-0.8.0/src/plugins/json/qjsonparser/json.cpp --- tiled-qt-0.7.1/src/plugins/json/qjsonparser/json.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/qjsonparser/json.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,446 @@ +/**************************************************************************** +** +** Copyright (c) 2010 Girish Ramakrishnan +** +** Use, modification and distribution is allowed without limitation, +** warranty, liability or support of any kind. +** +****************************************************************************/ + +#include "json.h" +#include "jsonparser.cpp" + +#include +#include + +/*! + \class JsonReader + \reentrant + + \brief The JsonReader class provides a fast parser for reading + well-formed JSON into a QVariant. + + The parser converts JSON types into QVariant types. For example, JSON + arrays are translated into QVariantList and JSON objects are translated + into QVariantMap. For example, + \code + JsonReader reader; + if (reader.parse(QString("{ \"id\": 123, \"class\": \"JsonReader\", \"authors\": [\"Denis\",\"Ettrich\",\"Girish\"] }"))) { + QVariant result = reader.result(); + QVariantMap map = result.toMap(); // the JSON object + qDebug() << map.count(); // 3 + qDebug() << map["id"].toInt(); // 123 + qDebug() << map["class"].toString(); // "JsonReader" + QVariantList list = map["authors"].toList(); + qDebug() << list[2].toString(); // "Girish" + } else { + qDebug() << reader.errorString(); + } + \endcode + + As seen above, the reader converts the JSON into a QVariant with arbitrary nesting. + A complete listing of JSON to QVariant conversion is documented at parse(). + + JsonWriter can be used to convert a QVariant into JSON string. +*/ + +/*! + Constructs a JSON reader. + */ +JsonReader::JsonReader() +{ +} + +/*! + Destructor + */ +JsonReader::~JsonReader() +{ +} + +/*! + Parses the JSON \a ba as a QVariant. + + If the parse succeeds, this function returns true and the QVariant can + be accessed using result(). If the parse fails, this function returns + false and the error message can be accessed using errorMessage(). + + The encoding of \ba is auto-detected based on the pattern of nulls in the + initial 4 octets as described in "Section 3. Encoding" of RFC 2647. If an + encoding could not be auto-detected, this function assumes UTF-8. + + The conversion from JSON type into QVariant type is as listed below: + \table + \row + \o false + \o QVariant::Bool with the value false. + \row + \o true + \o QVariant::Bool with the value true. + \row + \o null + \o QVariant::Invalid i.e QVariant() + \row + \o object + \o QVariant::Map i.e QVariantMap + \row + \o array + \o QVariant::List i.e QVariantList + \row + \o string + \o QVariant::String i.e QString + \row + \o number + \o QVariant::Double or QVariant::LongLong. If the JSON number contains a '.' or 'e' + or 'E', QVariant::Double is used. + \endtable + + The byte array \ba may or may not contain a BOM. + */ +bool JsonReader::parse(const QByteArray &ba) +{ + int mib = 106; // utf-8 + + QTextCodec *codec = QTextCodec::codecForUtfText(ba, 0); // try BOM detection + if (!codec) { + if (ba.length() > 3) { // auto-detect + const char *data = ba.constData(); + if (data[0] != 0) { + if (data[1] != 0) + mib = 106; // utf-8 + else if (data[2] != 0) + mib = 1014; // utf16 le + else + mib = 1019; // utf32 le + } else if (data[1] != 0) + mib = 1013; // utf16 be + else + mib = 1018; // utf32 be + } + codec = QTextCodec::codecForMib(mib); + } + QString str = codec->toUnicode(ba); + return parse(str); +} + +/*! + Parses the JSON string \a str as a QVariant. + + If the parse succeeds, this function returns true and the QVariant can + be accessed using result(). If the parse fails, this function returns + false and the error message can be accessed using errorMessage(). + */ +bool JsonReader::parse(const QString &str) +{ + JsonLexer lexer(str); + JsonParser parser; + if (!parser.parse(&lexer)) { + m_errorString = parser.errorMessage(); + m_result = QVariant(); + return false; + } + m_errorString.clear(); + m_result = parser.result(); + return true; +} + +/*! + Returns the result of the last parse() call. + + If parse() failed, this function returns an invalid QVariant. + */ +QVariant JsonReader::result() const +{ + return m_result; +} + +/*! + Returns the error message for the last parse() call. + + If parse() succeeded, this functions return an empty string. The error message + should be used for debugging purposes only. + */ +QString JsonReader::errorString() const +{ + return m_errorString; +} + +/*! + \class JsonWriter + \reentrant + + \brief The JsonWriter class converts a QVariant into a JSON string. + + The writer converts specific supported types stored in a QVariant into JSON. + For example, + \code + QVariantMap map; + map["id"] = 123; + map["class"] = "JsonWriter"; + QVariantList list; + list << "Denis" << "Ettrich" << "Girish"; + map["authors"] = list; + + JsonWriter writer; + if (writer.stringify(map)) { + QString json = writer.result(); + qDebug() << json; // {"authors": ["Denis", "Ettrich", "Girish"], "class": "JsonWriter", "id": 123 } + } else { + qDebug() << "Failed to stringify " << writer.errorString(); + } + \endcode + + The list of QVariant types that the writer supports is listed in stringify(). Note that + custom C++ types registered using Q_DECLARE_METATYPE are not supported. +*/ + +/*! + Creates a JsonWriter. + */ +JsonWriter::JsonWriter() + : m_autoFormatting(false), m_autoFormattingIndent(4, QLatin1Char(' ')) +{ +} + +/*! + Destructor. + */ +JsonWriter::~JsonWriter() +{ +} + +/*! + Enables auto formatting if \a enable is \c true, otherwise + disables it. + + When auto formatting is enabled, the writer automatically inserts + spaces and new lines to make the output more human readable. + + The default value is \c false. + */ +void JsonWriter::setAutoFormatting(bool enable) +{ + m_autoFormatting = enable; +} + +/*! + Returns \c true if auto formattting is enabled, otherwise \c false. + */ +bool JsonWriter::autoFormatting() const +{ + return m_autoFormatting; +} + +/*! + Sets the number of spaces or tabs used for indentation when + auto-formatting is enabled. Positive numbers indicate spaces, + negative numbers tabs. + + The default indentation is 4. + + \sa setAutoFormatting() +*/ +void JsonWriter::setAutoFormattingIndent(int spacesOrTabs) +{ + m_autoFormattingIndent = QString(qAbs(spacesOrTabs), QLatin1Char(spacesOrTabs >= 0 ? ' ' : '\t')); +} + +/*! + Retuns the numbers of spaces or tabs used for indentation when + auto-formatting is enabled. Positive numbers indicate spaces, + negative numbers tabs. + + The default indentation is 4. + + \sa setAutoFormatting() +*/ +int JsonWriter::autoFormattingIndent() const +{ + return m_autoFormattingIndent.count(QLatin1Char(' ')) - m_autoFormattingIndent.count(QLatin1Char('\t')); +} + +/*! \internal + Inserts escape character \ for characters in string as described in JSON specification. + */ +static QString escape(const QVariant &variant) +{ + QString str = variant.toString(); + QString res; + res.reserve(str.length()); + for (int i = 0; i < str.length(); i++) { + if (str[i] == QLatin1Char('\b')) { + res += QLatin1String("\\b"); + } else if (str[i] == QLatin1Char('\f')) { + res += QLatin1String("\\f"); + } else if (str[i] == QLatin1Char('\n')) { + res += QLatin1String("\\n"); + } else if (str[i] == QLatin1Char('\r')) { + res += QLatin1String("\\r"); + } else if (str[i] == QLatin1Char('\t')) { + res += QLatin1String("\\t"); + } else if (str[i] == QLatin1Char('\"')) { + res += QLatin1String("\\\""); + } else if (str[i] == QLatin1Char('\\')) { + res += QLatin1String("\\\\"); + } else if (str[i] == QLatin1Char('/')) { + res += QLatin1String("\\/"); + } else if (str[i].unicode() > 127) { + res += QLatin1String("\\u") + QString::number(str[i].unicode(), 16).rightJustified(4, QLatin1Char('0')); + } else { + res += str[i]; + } + } + return res; +} + +/*! \internal + Stringifies \a variant. + */ +void JsonWriter::stringify(const QVariant &variant, int depth) +{ + if (variant.type() == QVariant::List || variant.type() == QVariant::StringList) { + m_result += QLatin1Char('['); + QVariantList list = variant.toList(); + for (int i = 0; i < list.count(); i++) { + if (i != 0) { + m_result += QLatin1Char(','); + if (m_autoFormatting) + m_result += QLatin1Char(' '); + } + stringify(list[i], depth+1); + } + m_result += QLatin1Char(']'); + } else if (variant.type() == QVariant::Map) { + QString indent = m_autoFormattingIndent.repeated(depth); + QVariantMap map = variant.toMap(); + if (m_autoFormatting && depth != 0) { + m_result += QLatin1Char('\n'); + m_result += indent; + m_result += QLatin1String("{\n"); + } else { + m_result += QLatin1Char('{'); + } + for (QVariantMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it) { + if (it != map.constBegin()) { + m_result += QLatin1Char(','); + if (m_autoFormatting) + m_result += QLatin1Char('\n'); + } + if (m_autoFormatting) + m_result += indent + QLatin1Char(' '); + m_result += QLatin1Char('\"') + escape(it.key()) + QLatin1String("\":"); + stringify(it.value(), depth+1); + } + if (m_autoFormatting) { + m_result += QLatin1Char('\n'); + m_result += indent; + } + m_result += QLatin1Char('}'); + } else if (variant.type() == QVariant::String || variant.type() == QVariant::ByteArray) { + m_result += QLatin1Char('\"') + escape(variant) + QLatin1Char('\"'); + } else if (variant.type() == QVariant::Double || (int)variant.type() == (int)QMetaType::Float) { + double d = variant.toDouble(); + if (qIsFinite(d)) + m_result += QString::number(variant.toDouble(), 'g', 15); + else + m_result += QLatin1String("null"); + } else if (variant.type() == QVariant::Bool) { + m_result += variant.toBool() ? QLatin1String("true") : QLatin1String("false"); + } else if (variant.type() == QVariant::Invalid) { + m_result += QLatin1String("null"); + } else if (variant.type() == QVariant::ULongLong) { + m_result += QString::number(variant.toULongLong()); + } else if (variant.type() == QVariant::LongLong) { + m_result += QString::number(variant.toLongLong()); + } else if (variant.type() == QVariant::Int) { + m_result += QString::number(variant.toInt()); + } else if (variant.type() == QVariant::UInt) { + m_result += QString::number(variant.toUInt()); + } else if (variant.type() == QVariant::Char) { + QChar c = variant.toChar(); + if (c.unicode() > 127) + m_result += QLatin1String("\"\\u") + QString::number(c.unicode(), 16).rightJustified(4, QLatin1Char('0')) + QLatin1Char('\"'); + else + m_result += QLatin1Char('\"') + c + QLatin1Char('\"'); + } else if (variant.canConvert()) { + m_result += QString::number(variant.toLongLong()); + } else if (variant.canConvert()) { + m_result += QLatin1Char('\"') + escape(variant) + QLatin1Char('\"'); + } else { + if (!m_errorString.isEmpty()) + m_errorString.append(QLatin1Char('\n')); + QString msg = QString::fromLatin1("Unsupported type %1 (id: %2)").arg(QString::fromUtf8(variant.typeName())).arg(variant.userType()); + m_errorString.append(msg); + qWarning() << "JsonWriter::stringify - " << msg; + m_result += QLatin1String("null"); + } +} + +/*! + Converts the variant \a var into a JSON string. + + The stringizer converts \a var into JSON based on the type of it's contents. The supported + types and their conversion into JSON is as listed below: + + \table + \row + \o QVariant::List, QVariant::StringList + \o JSON array [] + \row + \o QVariant::Map + \o JSON object {} + \row + \o QVariant::String, QVariant::ByteArray + \o JSON string encapsulated in double quotes. String contents are escaped using '\' if necessary. + \row + \o QVariant::Double, QMetaType::Float + \o JSON number with a precision 15. Infinity and NaN are converted into null. + \row + \o QVariant::Bool + \o JSON boolean true and false + \row + \o QVariant::Invalid + \o JSON null + \row + \o QVariant::ULongLong, QVariant::LongLong, QVariant::Int, QVariant::UInt, + \o JSON number + \row + \o QVariant::Char + \o JSON string. Non-ASCII characters are converted into the \uXXXX notation. + \endtable + + As a fallback, the writer attempts to convert a type not listed above into a long long or a + QString using QVariant::canConvert. See the QVariant documentation for possible conversions. + + JsonWriter does not support stringizing custom user types stored in the QVariant. Any such + value would be converted into JSON null. + */ +bool JsonWriter::stringify(const QVariant &var) +{ + m_errorString.clear(); + m_result.clear(); + stringify(var, 0 /* depth */); + return m_errorString.isEmpty(); +} + +/*! + Returns the result of the last stringify() call. + + If stringify() failed, this function returns a null QString. + */ +QString JsonWriter::result() const +{ + return m_result; +} + +/*! + Returns the error message for the last stringify() call. + + If stringify() succeeded, this functions return an empty string. The error message + should be used for debugging purposes only. + */ +QString JsonWriter::errorString() const +{ + return m_errorString; +} + diff -Nru tiled-qt-0.7.1/src/plugins/json/qjsonparser/json.h tiled-qt-0.8.0/src/plugins/json/qjsonparser/json.h --- tiled-qt-0.7.1/src/plugins/json/qjsonparser/json.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/qjsonparser/json.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (c) 2010 Girish Ramakrishnan +** +** Use, modification and distribution is allowed without limitation, +** warranty, liability or support of any kind. +** +****************************************************************************/ + +#ifndef JSON_H +#define JSON_H + +#include +#include + +class JsonReader +{ +public: + JsonReader(); + ~JsonReader(); + + bool parse(const QByteArray &ba); + bool parse(const QString &str); + + QVariant result() const; + + QString errorString() const; + +private: + QVariant m_result; + QString m_errorString; + void *reserved; +}; + +class JsonWriter +{ +public: + JsonWriter(); + ~JsonWriter(); + + bool stringify(const QVariant &variant); + + QString result() const; + + QString errorString() const; + + void setAutoFormatting(bool autoFormat); + bool autoFormatting() const; + + void setAutoFormattingIndent(int spaceOrTabs); + int autoFormattingIndent() const; + +private: + void stringify(const QVariant &variant, int depth); + + QString m_result; + QString m_errorString; + bool m_autoFormatting; + QString m_autoFormattingIndent; + void *reserved; +}; + +#endif // JSON_H + diff -Nru tiled-qt-0.7.1/src/plugins/json/qjsonparser/jsonparser.cpp tiled-qt-0.8.0/src/plugins/json/qjsonparser/jsonparser.cpp --- tiled-qt-0.7.1/src/plugins/json/qjsonparser/jsonparser.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/qjsonparser/jsonparser.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,527 @@ +// This file was generated by qlalr - DO NOT EDIT! +#ifndef JSONPARSER_CPP +#define JSONPARSER_CPP + +class JsonGrammar +{ +public: + enum VariousConstants { + EOF_SYMBOL = 0, + ERROR = 12, + T_COLON = 7, + T_COMMA = 8, + T_FALSE = 9, + T_LCURLYBRACKET = 3, + T_LSQUAREBRACKET = 5, + T_NULL = 11, + T_NUMBER = 2, + T_RCURLYBRACKET = 4, + T_RSQUAREBRACKET = 6, + T_STRING = 1, + T_TRUE = 10, + + ACCEPT_STATE = 12, + RULE_COUNT = 17, + STATE_COUNT = 27, + TERMINAL_COUNT = 13, + NON_TERMINAL_COUNT = 7, + + GOTO_INDEX_OFFSET = 27, + GOTO_INFO_OFFSET = 37, + GOTO_CHECK_OFFSET = 37 + }; + + static const char *const spell []; + static const short lhs []; + static const short rhs []; + +#ifndef QLALR_NO_JSONGRAMMAR_DEBUG_INFO + static const int rule_index []; + static const int rule_info []; +#endif // QLALR_NO_JSONGRAMMAR_DEBUG_INFO + + static const short goto_default []; + static const short action_default []; + static const short action_index []; + static const short action_info []; + static const short action_check []; + + static inline int nt_action (int state, int nt) + { + const int yyn = action_index [GOTO_INDEX_OFFSET + state] + nt; + if (yyn < 0 || action_check [GOTO_CHECK_OFFSET + yyn] != nt) + return goto_default [nt]; + + return action_info [GOTO_INFO_OFFSET + yyn]; + } + + static inline int t_action (int state, int token) + { + const int yyn = action_index [state] + token; + + if (yyn < 0 || action_check [yyn] != token) + return - action_default [state]; + + return action_info [yyn]; + } +}; + + +const char *const JsonGrammar::spell [] = { + "end of file", "string", "number", "{", "}", "[", "]", ":", ",", "false", + "true", "null", "error", +#ifndef QLALR_NO_JSONGRAMMAR_DEBUG_INFO +"Root", "Value", "Object", "Members", "Array", "Values", "$accept" +#endif // QLALR_NO_JSONGRAMMAR_DEBUG_INFO +}; + +const short JsonGrammar::lhs [] = { + 13, 15, 16, 16, 16, 14, 14, 14, 14, 14, + 14, 14, 17, 18, 18, 18, 19}; + +const short JsonGrammar::rhs [] = { + 1, 3, 3, 5, 0, 1, 1, 1, 1, 1, + 1, 1, 3, 1, 3, 0, 2}; + + +#ifndef QLALR_NO_JSONGRAMMAR_DEBUG_INFO +const int JsonGrammar::rule_info [] = { + 13, 14 + , 15, 3, 16, 4 + , 16, 1, 7, 14 + , 16, 16, 8, 1, 7, 14 + , 16 + , 14, 9 + , 14, 10 + , 14, 11 + , 14, 15 + , 14, 17 + , 14, 2 + , 14, 1 + , 17, 5, 18, 6 + , 18, 14 + , 18, 18, 8, 14 + , 18 + , 19, 13, 0}; + +const int JsonGrammar::rule_index [] = { + 0, 2, 6, 10, 16, 17, 19, 21, 23, 25, + 27, 29, 31, 35, 37, 41, 42}; +#endif // QLALR_NO_JSONGRAMMAR_DEBUG_INFO + +const short JsonGrammar::action_default [] = { + 0, 10, 9, 0, 6, 5, 16, 8, 11, 12, + 7, 1, 17, 0, 0, 0, 2, 0, 0, 4, + 0, 3, 14, 0, 0, 13, 15}; + +const short JsonGrammar::goto_default [] = { + 3, 11, 2, 13, 1, 23, 0}; + +const short JsonGrammar::action_index [] = { + 24, -13, -13, 12, -13, -1, 24, -13, -13, -13, + -13, -13, -13, 1, -5, 2, -13, -6, 24, -13, + 24, -13, -13, -2, 24, -13, -13, + + -7, -7, -7, -7, -7, -7, 1, -7, -7, -7, + -7, -7, -7, -7, -7, -7, -7, -7, 0, -7, + -1, -7, -7, -7, 5, -7, -7}; + +const short JsonGrammar::action_info [] = { + 14, 18, 20, 17, 25, 16, 24, 0, 0, 15, + 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 9, 8, 5, 0, 6, + 0, 0, 0, 4, 10, 7, 0, + + 21, 19, 22, 0, 0, 0, 26, 0, 0, 0, + 0, 0}; + +const short JsonGrammar::action_check [] = { + 1, 7, 7, 1, 6, 4, 8, -1, -1, 8, + -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 1, 2, 3, -1, 5, + -1, -1, -1, 9, 10, 11, -1, + + 1, 1, 1, -1, -1, -1, 1, -1, -1, -1, + -1, -1}; + + +#line 29 "json.g" + +/**************************************************************************** +** +** Copyright (c) 2010 Girish Ramakrishnan +** Copyright (c) 2011 Denis Dzyubenko +** +** Use, modification and distribution is allowed without limitation, +** warranty, liability or support of any kind. +** +****************************************************************************/ + +#ifndef JSONPARSER_P_H +#define JSONPARSER_P_H + +#include +#include +#include + +class JsonLexer +{ +public: + JsonLexer(const QString &string); + ~JsonLexer(); + + int lex(); + QVariant symbol() const { return m_symbol; } + int lineNumber() const { return m_lineNumber; } + int pos() const { return m_pos; } + +private: + int parseNumber(); + int parseString(); + int parseKeyword(); + + QString m_strdata; + int m_lineNumber; + int m_pos; + QVariant m_symbol; +}; + +class JsonParser : protected JsonGrammar +{ +public: + JsonParser(); + ~JsonParser(); + + bool parse(JsonLexer *lex); + QVariant result() const { return m_result; } + QString errorMessage() const { return QString::fromLatin1("%1 at line %2 pos %3").arg(m_errorMessage).arg(m_errorLineNumber).arg(m_errorPos); } + +private: + void reallocateStack(); + + inline QVariant &sym(int index) + { return m_symStack[m_tos + index - 1]; } + inline QVariantMap &map(int index) + { return m_mapStack[m_tos + index - 1]; } + inline QVariantList &list(int index) + { return m_listStack[m_tos + index - 1]; } + + int m_tos; + QVector m_stateStack; + QVector m_symStack; + QVector m_mapStack; + QVector m_listStack; + QString m_errorMessage; + int m_errorLineNumber; + int m_errorPos; + QVariant m_result; +}; + +#endif // JSONPARSER_P_H + +#line 103 "json.g" + +/**************************************************************************** +** +** Copyright (c) 2010 Girish Ramakrishnan +** Copyright (c) 2011 Denis Dzyubenko +** +** Use, modification and distribution is allowed without limitation, +** warranty, liability or support of any kind. +** +****************************************************************************/ + +#include + +#define L1C(c) ushort(uchar(c)) + +JsonLexer::JsonLexer(const QString &str) + : m_strdata(str), m_lineNumber(1), m_pos(0) +{ +} + + + +JsonLexer::~JsonLexer() +{ +} + +int JsonLexer::parseString() +{ + bool esc = false; + ++m_pos; // skip initial " + int start = m_pos; + + const ushort *uc = m_strdata.utf16(); + const int remaining = m_strdata.length() - m_pos; + int i; + for (i = 0; i < remaining; ++i) { + const ushort c = uc[m_pos + i]; + if (c == L1C('\"')) { + m_symbol = QString((QChar *)uc + m_pos, i); + m_pos += i; + ++m_pos; // eat quote + return JsonGrammar::T_STRING; + } else if (c == L1C('\\')) { + ++m_pos; // eat backslash + esc = true; + break; + } + } + + QString str; + if (i) { + str.resize(i); + memcpy((char *)str.utf16(), uc + start, i * 2); + m_pos += i; + } + + for (; m_pos < m_strdata.length(); ++m_pos) { + const ushort c = uc[m_pos]; + if (esc) { + if (c == L1C('b')) str += QLatin1Char('\b'); + else if (c == L1C('f')) str += QLatin1Char('\f'); + else if (c == L1C('n')) str += QLatin1Char('\n'); + else if (c == L1C('r')) str += QLatin1Char('\r'); + else if (c == L1C('t')) str += QLatin1Char('\t'); + else if (c == L1C('\\')) str += QLatin1Char('\\'); + else if (c == L1C('\"')) str += QLatin1Char('\"'); + else if (c == L1C('u') && m_pos+4= L1C('0') && c <= L1C('9')) { + if (!isDouble) { + value *= 10; + value += c - L1C('0'); + } + continue; + } + break; + } + if (!isDouble) { + m_symbol = value * negative; + } else { + QString number = QString::fromRawData((QChar *)uc+start, m_pos-start); + m_symbol = number.toDouble(); + } + return JsonGrammar::T_NUMBER; +} + +int JsonLexer::parseKeyword() +{ + int start = m_pos; + for (; m_pos < m_strdata.length(); ++m_pos) { + const ushort c = m_strdata.at(m_pos).unicode(); + if (c >= L1C('a') && c <= L1C('z')) + continue; + break; + } + const ushort *k = (const ushort *)m_strdata.constData() + start; + const int l = m_pos-start; + if (l == 4) { + static const ushort true_data[] = { 't', 'r', 'u', 'e' }; + static const ushort null_data[] = { 'n', 'u', 'l', 'l' }; + if (!memcmp(k, true_data, 4)) + return JsonGrammar::T_TRUE; + if (!memcmp(k, null_data, 4)) + return JsonGrammar::T_NULL; + } else if (l == 5) { + static const ushort false_data[] = { 'f', 'a', 'l', 's', 'e' }; + if (!memcmp(k, false_data, 5)) + return JsonGrammar::T_FALSE; + } + return JsonGrammar::ERROR; +} + +int JsonLexer::lex() +{ + m_symbol.clear(); + + const ushort *uc = m_strdata.utf16(); + const int len = m_strdata.length(); + while (m_pos < len) { + const ushort c = uc[m_pos]; + switch (c) { + case L1C('['): ++m_pos; return JsonGrammar::T_LSQUAREBRACKET; + case L1C(']'): ++m_pos; return JsonGrammar::T_RSQUAREBRACKET; + case L1C('{'): ++m_pos; return JsonGrammar::T_LCURLYBRACKET; + case L1C('}'): ++m_pos; return JsonGrammar::T_RCURLYBRACKET; + case L1C(':'): ++m_pos; return JsonGrammar::T_COLON; + case L1C(','): ++m_pos; return JsonGrammar::T_COMMA; + case L1C(' '): case L1C('\r'): case L1C('\t'): ++m_pos; break; + case L1C('\n'): ++m_pos; ++m_lineNumber; break; + case L1C('"'): return parseString(); + default: + if (c == L1C('+') || c == L1C('-') || (c >= L1C('0') && c <= L1C('9'))) { + return parseNumber(); + } + if (c >= L1C('a') && c <= L1C('z')) { + return parseKeyword(); + } + return JsonGrammar::ERROR; + } + } + return JsonGrammar::EOF_SYMBOL; +} +#undef L1C + +JsonParser::JsonParser() +{ +} + +JsonParser::~JsonParser() +{ +} + +void JsonParser::reallocateStack() +{ + int size = m_stateStack.size(); + if (size == 0) + size = 128; + else + size <<= 1; + + m_symStack.resize(size); + m_mapStack.resize(size); + m_listStack.resize(size); + m_stateStack.resize(size); +} + +bool JsonParser::parse(JsonLexer *lexer) +{ + const int INITIAL_STATE = 0; + int yytoken = -1; + reallocateStack(); + m_tos = 0; + m_stateStack[++m_tos] = INITIAL_STATE; + + while (true) { + const int state = m_stateStack[m_tos]; + if (yytoken == -1 && -TERMINAL_COUNT != action_index[state]) { + yytoken = lexer->lex(); + } + int act = t_action(state, yytoken); + if (act == ACCEPT_STATE) + return true; + else if (act > 0) { + if (++m_tos == m_stateStack.size()) + reallocateStack(); + m_stateStack[m_tos] = act; + m_symStack[m_tos] = lexer->symbol(); + yytoken = -1; + } else if (act < 0) { + int r = -act-1; + m_tos -= rhs[r]; + act = m_stateStack.at(m_tos++); + switch (r) { + +#line 337 "json.g" + case 0: { m_result = sym(1); break; } +#line 340 "json.g" + case 1: { sym(1) = map(2); break; } +#line 343 "json.g" + case 2: { QVariantMap m; m.insert(sym(1).toString(), sym(3)); map(1) = m; break; } +#line 346 "json.g" + case 3: { map(1).insert(sym(3).toString(), sym(5)); break; } +#line 349 "json.g" + case 4: { map(1) = QVariantMap(); break; } +#line 352 "json.g" + case 5: { sym(1) = QVariant(false); break; } +#line 355 "json.g" + case 6: { sym(1) = QVariant(true); break; } +#line 364 "json.g" + case 12: { sym(1) = list(2); break; } +#line 367 "json.g" + case 13: { QVariantList l; l.append(sym(1)); list(1) = l; break; } +#line 370 "json.g" + case 14: { list(1).append(sym(3)); break; } +#line 373 "json.g" + case 15: { list(1) = QVariantList(); break; } +#line 375 "json.g" + + } // switch + m_stateStack[m_tos] = nt_action(act, lhs[r] - TERMINAL_COUNT); + } else { + int ers = state; + int shifts = 0; + int reduces = 0; + int expected_tokens[3]; + for (int tk = 0; tk < TERMINAL_COUNT; ++tk) { + int k = t_action(ers, tk); + + if (! k) + continue; + else if (k < 0) + ++reduces; + else if (spell[tk]) { + if (shifts < 3) + expected_tokens[shifts] = tk; + ++shifts; + } + } + + m_errorLineNumber = lexer->lineNumber(); + m_errorPos = lexer->pos(); + m_errorMessage.clear(); + if (shifts && shifts < 3) { + bool first = true; + + for (int s = 0; s < shifts; ++s) { + if (first) + m_errorMessage += QLatin1String("Expected "); + else + m_errorMessage += QLatin1String(", "); + + first = false; + m_errorMessage += QLatin1String("'"); + m_errorMessage += QLatin1String(spell[expected_tokens[s]]); + m_errorMessage += QLatin1String("'"); + } + } + return false; + } + } + + return false; +} + + +#endif // JSONPARSER_CPP + diff -Nru tiled-qt-0.7.1/src/plugins/json/qjsonparser/qjsonparser.pri tiled-qt-0.8.0/src/plugins/json/qjsonparser/qjsonparser.pri --- tiled-qt-0.7.1/src/plugins/json/qjsonparser/qjsonparser.pri 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/qjsonparser/qjsonparser.pri 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,6 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +SOURCES += json.cpp +HEADERS += json.h + diff -Nru tiled-qt-0.7.1/src/plugins/json/varianttomapconverter.cpp tiled-qt-0.8.0/src/plugins/json/varianttomapconverter.cpp --- tiled-qt-0.7.1/src/plugins/json/varianttomapconverter.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/varianttomapconverter.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,316 @@ +/* + * JSON Tiled Plugin + * Copyright 2011, Porfírio José Pereira Ribeiro + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 "varianttomapconverter.h" + +#include "map.h" +#include "mapobject.h" +#include "objectgroup.h" +#include "properties.h" +#include "tile.h" +#include "tilelayer.h" +#include "tileset.h" + +using namespace Tiled; +using namespace Json; + +Map *VariantToMapConverter::toMap(const QVariant &variant, + const QDir &mapDir) +{ + mGidMapper.clear(); + mMapDir = mapDir; + + const QVariantMap variantMap = variant.toMap(); + const QString orientationString = variantMap["orientation"].toString(); + + Map::Orientation orientation = orientationFromString(orientationString); + + if (orientation == Map::Unknown) { + mError = tr("Unsupported map orientation: \"%1\"") + .arg(orientationString); + return 0; + } + + mMap = new Map(orientation, + variantMap["width"].toInt(), + variantMap["height"].toInt(), + variantMap["tilewidth"].toInt(), + variantMap["tileheight"].toInt()); + + mMap->setProperties(toProperties(variantMap["properties"])); + + foreach (const QVariant &tilesetVariant, variantMap["tilesets"].toList()) { + Tileset *tileset = toTileset(tilesetVariant); + if (!tileset) { + // Delete tilesets loaded so far and the map + qDeleteAll(mMap->tilesets()); + delete mMap; + return 0; + } + + mMap->addTileset(tileset); + } + + foreach (const QVariant &layerVariant, variantMap["layers"].toList()) + if (Layer *layer = toLayer(layerVariant)) + mMap->addLayer(layer); + + return mMap; +} + +Properties VariantToMapConverter::toProperties(const QVariant &variant) +{ + const QVariantMap variantMap = variant.toMap(); + + Properties properties; + + QVariantMap::const_iterator it = variantMap.constBegin(); + QVariantMap::const_iterator it_end = variantMap.constEnd(); + for (; it != it_end; ++it) + properties[it.key()] = it.value().toString(); + + return properties; +} + +Tileset *VariantToMapConverter::toTileset(const QVariant &variant) +{ + const QVariantMap variantMap = variant.toMap(); + + const int firstGid = variantMap["firstgid"].toInt(); + const QString name = variantMap["name"].toString(); + const int tileWidth = variantMap["tilewidth"].toInt(); + const int tileHeight = variantMap["tileheight"].toInt(); + const int spacing = variantMap["spacing"].toInt(); + const int margin = variantMap["margin"].toInt(); + + if (tileWidth <= 0 || tileHeight <= 0 || firstGid == 0) { + mError = tr("Invalid tileset parameters for tileset '%1'").arg(name); + return 0; + } + + Tileset *tileset = new Tileset(name, + tileWidth, tileHeight, + spacing, margin); + + const QString trans = variantMap["transparentcolor"].toString(); + if (!trans.isEmpty()) +#if QT_VERSION >= 0x040700 + if (QColor::isValidColor(trans)) +#endif + tileset->setTransparentColor(QColor(trans)); + + QString imageSource = variantMap["image"].toString(); + + if (QDir::isRelativePath(imageSource)) + imageSource = mMapDir.path() + QLatin1Char('/') + imageSource; + + if (!tileset->loadFromImage(QImage(imageSource), imageSource)) { + mError = tr("Error loading tileset image:\n'%1'").arg(imageSource); + delete tileset; + return 0; + } + + tileset->setProperties(toProperties(variantMap["properties"])); + + QVariantMap propertiesVariantMap = variantMap["tileproperties"].toMap(); + QVariantMap::const_iterator it = propertiesVariantMap.constBegin(); + for (; it != propertiesVariantMap.constEnd(); ++it) { + const int tileIndex = it.key().toInt(); + const QVariant propertiesVar = it.value(); + if (tileIndex >= 0 && tileIndex < tileset->tileCount()) { + const Properties properties = toProperties(propertiesVar); + tileset->tileAt(tileIndex)->setProperties(properties); + } + } + + mGidMapper.insert(firstGid, tileset); + return tileset; +} + +Layer *VariantToMapConverter::toLayer(const QVariant &variant) +{ + const QVariantMap variantMap = variant.toMap(); + Layer *layer = 0; + + if (variantMap["type"] == "tilelayer") + layer = toTileLayer(variantMap); + else if (variantMap["type"] == "objectgroup") + layer = toObjectGroup(variantMap); + + if (layer) + layer->setProperties(toProperties(variantMap["properties"])); + + return layer; +} + +TileLayer *VariantToMapConverter::toTileLayer(const QVariantMap &variantMap) +{ + const QString name = variantMap["name"].toString(); + const int width = variantMap["width"].toInt(); + const int height = variantMap["height"].toInt(); + const QVariantList dataVariantList = variantMap["data"].toList(); + + if (dataVariantList.size() != width * height) { + mError = tr("Corrupt layer data for layer '%1'").arg(name); + return 0; + } + + TileLayer *tileLayer = new TileLayer(name, + variantMap["x"].toInt(), + variantMap["y"].toInt(), + width, height); + + const qreal opacity = variantMap["opacity"].toReal(); + const bool visible = variantMap["visible"].toBool(); + + tileLayer->setOpacity(opacity); + tileLayer->setVisible(visible); + + int x = 0; + int y = 0; + bool ok; + + foreach (const QVariant &gidVariant, dataVariantList) { + const uint gid = gidVariant.toUInt(&ok); + if (!ok) { + mError = tr("Unable to parse tile at (%1,%2) on layer '%3'") + .arg(x).arg(y).arg(tileLayer->name()); + + delete tileLayer; + tileLayer = 0; + break; + } + + const Cell cell = mGidMapper.gidToCell(gid, ok); + + tileLayer->setCell(x, y, cell); + + x++; + if (x >= tileLayer->width()) { + x = 0; + y++; + } + } + + return tileLayer; +} + +class PixelToTileCoordinates +{ +public: + PixelToTileCoordinates(const Map *map) + { + if (map->orientation() == Map::Isometric) { + // Isometric needs special handling, since the pixel values are + // based solely on the tile height. + mMultiplierX = (qreal) 1 / map->tileHeight(); + mMultiplierY = (qreal) 1 / map->tileHeight(); + } else { + mMultiplierX = (qreal) 1 / map->tileWidth(); + mMultiplierY = (qreal) 1 / map->tileHeight(); + } + } + + QPointF operator() (int x, int y) const + { + return QPointF(x * mMultiplierX, + y * mMultiplierY); + } + +private: + qreal mMultiplierX; + qreal mMultiplierY; +}; + +ObjectGroup *VariantToMapConverter::toObjectGroup(const QVariantMap &variantMap) +{ + ObjectGroup *objectGroup = new ObjectGroup(variantMap["name"].toString(), + variantMap["x"].toInt(), + variantMap["y"].toInt(), + variantMap["width"].toInt(), + variantMap["height"].toInt()); + + const qreal opacity = variantMap["opacity"].toReal(); + const bool visible = variantMap["visible"].toBool(); + + objectGroup->setOpacity(opacity); + objectGroup->setVisible(visible); + + objectGroup->setColor(variantMap.value("color").value()); + + const PixelToTileCoordinates toTile(mMap); + + foreach (const QVariant &objectVariant, variantMap["objects"].toList()) { + const QVariantMap objectVariantMap = objectVariant.toMap(); + + const QString name = objectVariantMap["name"].toString(); + const QString type = objectVariantMap["type"].toString(); + const int gid = objectVariantMap["gid"].toInt(); + const int x = objectVariantMap["x"].toInt(); + const int y = objectVariantMap["y"].toInt(); + const int width = objectVariantMap["width"].toInt(); + const int height = objectVariantMap["height"].toInt(); + + const QPointF pos = toTile(x, y); + const QPointF size = toTile(width, height); + + MapObject *object = new MapObject(name, type, + pos, + QSizeF(size.x(), size.y())); + + if (gid) { + bool ok; + const Cell cell = mGidMapper.gidToCell(gid, ok); + object->setTile(cell.tile); + } + + object->setProperties(toProperties(objectVariantMap["properties"])); + objectGroup->addObject(object); + + const QVariant polylineVariant = objectVariantMap["polyline"]; + const QVariant polygonVariant = objectVariantMap["polygon"]; + + if (polygonVariant.isValid()) { + object->setShape(MapObject::Polygon); + object->setPolygon(toPolygon(polygonVariant)); + } + if (polylineVariant.isValid()) { + object->setShape(MapObject::Polyline); + object->setPolygon(toPolygon(polylineVariant)); + } + } + + return objectGroup; +} + +QPolygonF VariantToMapConverter::toPolygon(const QVariant &variant) const +{ + const PixelToTileCoordinates toTile(mMap); + + QPolygonF polygon; + foreach (const QVariant &pointVariant, variant.toList()) { + const QVariantMap pointVariantMap = pointVariant.toMap(); + const int pointX = pointVariantMap["x"].toInt(); + const int pointY = pointVariantMap["y"].toInt(); + polygon.append(toTile(pointX, pointY)); + } + return polygon; +} diff -Nru tiled-qt-0.7.1/src/plugins/json/varianttomapconverter.h tiled-qt-0.8.0/src/plugins/json/varianttomapconverter.h --- tiled-qt-0.7.1/src/plugins/json/varianttomapconverter.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/json/varianttomapconverter.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,84 @@ +/* + * JSON Tiled Plugin + * Copyright 2011, Porfírio José Pereira Ribeiro + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 . + */ + +#ifndef VARIANTTOMAPCONVERTER_H +#define VARIANTTOMAPCONVERTER_H + +#include "gidmapper.h" + +#include +#include +#include + +namespace Tiled { +class Layer; +class Map; +class ObjectGroup; +class Properties; +class Tileset; +} + +namespace Json { + +/** + * Converts a QVariant to a Map instance. Meant to be used together with + * JsonReader. + */ +class VariantToMapConverter +{ + // Using the MapReader context since the messages are the same + Q_DECLARE_TR_FUNCTIONS(MapReader) + +public: + VariantToMapConverter() {} + + /** + * Tries to convert the given \a variant to a Map instance. The \a mapDir + * is necessary to resolve any relative references to external images. + * + * Returns 0 in case of an error. The error can be obstained using + * errorString(). + */ + Tiled::Map *toMap(const QVariant &variant, const QDir &mapDir); + + /** + * Returns the last error, if any. + */ + QString errorString() const { return mError; } + +private: + Tiled::Properties toProperties(const QVariant &variant); + Tiled::Tileset *toTileset(const QVariant &variant); + Tiled::Layer *toLayer(const QVariant &variant); + Tiled::TileLayer *toTileLayer(const QVariantMap &variantMap); + Tiled::ObjectGroup *toObjectGroup(const QVariantMap &variantMap); + + QPolygonF toPolygon(const QVariant &variant) const; + + Tiled::Map *mMap; + QDir mMapDir; + Tiled::GidMapper mGidMapper; + QString mError; +}; + +} // namespace Json + +#endif // VARIANTTOMAPCONVERTER_H diff -Nru tiled-qt-0.7.1/src/plugins/lua/luaplugin.cpp tiled-qt-0.8.0/src/plugins/lua/luaplugin.cpp --- tiled-qt-0.7.1/src/plugins/lua/luaplugin.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/lua/luaplugin.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -32,6 +32,14 @@ #include +/** + * See below for an explanation of the different formats. One of these needs + * to be defined. + */ +#define POLYGON_FORMAT_FULL +//#define POLYGON_FORMAT_PAIRS +//#define POLYGON_FORMAT_OPTIMAL + using namespace Lua; using namespace Tiled; @@ -42,7 +50,7 @@ bool LuaPlugin::write(const Map *map, const QString &fileName) { QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { mError = tr("Could not open file for writing."); return false; } @@ -74,20 +82,7 @@ writer.writeKeyAndValue("version", "1.1"); writer.writeKeyAndValue("luaversion", "5.1"); - const char *orientation = "unknown"; - switch (map->orientation()) { - case Map::Unknown: - break; - case Map::Orthogonal: - orientation = "orthogonal"; - break; - case Map::Isometric: - orientation = "isometric"; - break; - case Map::Hexagonal: - orientation = "hexagonal"; - break; - } + const QString orientation = orientationToString(map->orientation()); writer.writeKeyAndValue("orientation", orientation); writer.writeKeyAndValue("width", map->width()); @@ -159,6 +154,8 @@ writer.writeKeyAndValue("transparentColor", tileset->transparentColor().name()); } + + writeProperties(writer, tileset->properties()); } writer.writeStartTable("tiles"); @@ -226,6 +223,34 @@ writer.writeEndTable(); } +// TODO: Unduplicate this class since it's used also in mapwriter.cpp +class TileToPixelCoordinates +{ +public: + TileToPixelCoordinates(Map *map) + { + if (map->orientation() == Map::Isometric) { + // Isometric needs special handling, since the pixel values are + // based solely on the tile height. + mMultiplierX = map->tileHeight(); + mMultiplierY = map->tileHeight(); + } else { + mMultiplierX = map->tileWidth(); + mMultiplierY = map->tileHeight(); + } + } + + QPoint operator() (qreal x, qreal y) const + { + return QPoint(qRound(x * mMultiplierX), + qRound(y * mMultiplierY)); + } + +private: + int mMultiplierX; + int mMultiplierY; +}; + void LuaPlugin::writeMapObject(LuaTableWriter &writer, const Tiled::MapObject *mapObject) { @@ -233,35 +258,97 @@ writer.writeKeyAndValue("name", mapObject->name()); writer.writeKeyAndValue("type", mapObject->type()); - // Convert from tile to pixel coordinates const ObjectGroup *objectGroup = mapObject->objectGroup(); - const Map *map = objectGroup->map(); - const int tileHeight = map->tileHeight(); - const int tileWidth = map->tileWidth(); - const QRectF bounds = mapObject->bounds(); - - int x, y, width, height; - - if (map->orientation() == Map::Isometric) { - // Isometric needs special handling, since the pixel values are based - // solely on the tile height. - x = qRound(bounds.x() * tileHeight); - y = qRound(bounds.y() * tileHeight); - width = qRound(bounds.width() * tileHeight); - height = qRound(bounds.height() * tileHeight); - } else { - x = qRound(bounds.x() * tileWidth); - y = qRound(bounds.y() * tileHeight); - width = qRound(bounds.width() * tileWidth); - height = qRound(bounds.height() * tileHeight); - } + const TileToPixelCoordinates toPixel(objectGroup->map()); + + const QPoint pos = toPixel(mapObject->x(), mapObject->y()); + const QPoint size = toPixel(mapObject->width(), mapObject->height()); + + writer.writeKeyAndValue("x", pos.x()); + writer.writeKeyAndValue("y", pos.y()); + writer.writeKeyAndValue("width", size.x()); + writer.writeKeyAndValue("height", size.y()); - writer.writeKeyAndValue("x", x); - writer.writeKeyAndValue("y", y); - writer.writeKeyAndValue("width", width); - writer.writeKeyAndValue("height", height); if (Tile *tile = mapObject->tile()) writer.writeKeyAndValue("gid", mGidMapper.cellToGid(Cell(tile))); + + const QPolygonF &polygon = mapObject->polygon(); + if (!polygon.isEmpty()) { + if (mapObject->shape() == MapObject::Polygon) + writer.writeStartTable("polygon"); + else + writer.writeStartTable("polyline"); + +#if defined(POLYGON_FORMAT_FULL) + /* This format is the easiest to read and understand: + * + * { + * { x = 1, y = 1 }, + * { x = 2, y = 2 }, + * { x = 3, y = 3 }, + * ... + * } + */ + foreach (const QPointF &point, polygon) { + writer.writeStartTable(); + writer.setSuppressNewlines(true); + + const QPoint pixelCoordinates = toPixel(point.x(), point.y()); + writer.writeKeyAndValue("x", pixelCoordinates.x()); + writer.writeKeyAndValue("y", pixelCoordinates.y()); + + writer.writeEndTable(); + writer.setSuppressNewlines(false); + } +#elif defined(POLYGON_FORMAT_PAIRS) + /* This is an alternative that takes about 25% less memory. + * + * { + * { 1, 1 }, + * { 2, 2 }, + * { 3, 3 }, + * ... + * } + */ + foreach (const QPointF &point, polygon) { + writer.writeStartTable(); + writer.setSuppressNewlines(true); + + const QPoint pixelCoordinates = toPixel(point.x(), point.y()); + writer.writeValue(pixelCoordinates.x()); + writer.writeValue(pixelCoordinates.y()); + + writer.writeEndTable(); + writer.setSuppressNewlines(false); + } +#elif defined(POLYGON_FORMAT_OPTIMAL) + /* Writing it out in two tables, one for the x coordinates and one for + * the y coordinates. It is a compromise between code readability and + * performance. This takes the least amount of memory (60% less than + * the first approach). + * + * x = { 1, 2, 3, ... } + * y = { 1, 2, 3, ... } + */ + + writer.writeStartTable("x"); + writer.setSuppressNewlines(true); + foreach (const QPointF &point, polygon) + writer.writeValue(toPixel(point.x(), point.y()).x()); + writer.writeEndTable(); + writer.setSuppressNewlines(false); + + writer.writeStartTable("y"); + writer.setSuppressNewlines(true); + foreach (const QPointF &point, polygon) + writer.writeValue(toPixel(point.x(), point.y()).y()); + writer.writeEndTable(); + writer.setSuppressNewlines(false); +#endif + + writer.writeEndTable(); + } + writeProperties(writer, mapObject->properties()); writer.writeEndTable(); diff -Nru tiled-qt-0.7.1/src/plugins/lua/luatablewriter.cpp tiled-qt-0.8.0/src/plugins/lua/luatablewriter.cpp --- tiled-qt-0.7.1/src/plugins/lua/luatablewriter.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/lua/luatablewriter.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -75,12 +75,8 @@ void LuaTableWriter::writeEndTable() { --m_indent; - - if (!m_newLine) { - write('\n'); - writeIndent(); - } - + if (m_valueWritten) + writeNewline(); write('}'); m_newLine = false; m_valueWritten = true; @@ -158,21 +154,13 @@ write(m_valueSeparator); m_valueWritten = false; } - if (!m_newLine) { - write('\n'); - writeIndent(); - m_newLine = true; - } + writeNewline(); } void LuaTableWriter::prepareNewValue() { if (!m_valueWritten) { - if (!m_newLine) { - write('\n'); - writeIndent(); - m_newLine = true; - } + writeNewline(); } else { write(m_valueSeparator); write(' '); @@ -185,6 +173,19 @@ write(" "); } +void LuaTableWriter::writeNewline() +{ + if (!m_newLine) { + if (m_suppressNewlines) { + write(' '); + } else { + write('\n'); + writeIndent(); + } + m_newLine = true; + } +} + void LuaTableWriter::write(const char *bytes, uint length) { if (m_device->write(bytes, length) != length) diff -Nru tiled-qt-0.7.1/src/plugins/lua/luatablewriter.h tiled-qt-0.8.0/src/plugins/lua/luatablewriter.h --- tiled-qt-0.7.1/src/plugins/lua/luatablewriter.h 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/lua/luatablewriter.h 2011-12-11 21:49:29.000000000 +0000 @@ -63,6 +63,9 @@ void writeKeyAndUnquotedValue(const QByteArray &key, const QByteArray &value); + void setSuppressNewlines(bool suppressNewlines); + bool suppressNewlines() const; + void prepareNewLine(); bool hasError() const { return m_error; } @@ -71,6 +74,7 @@ void prepareNewValue(); void writeIndent(); + void writeNewline(); void write(const char *bytes, uint length); void write(const char *bytes); void write(const QByteArray &bytes); @@ -79,6 +83,7 @@ QIODevice *m_device; int m_indent; char m_valueSeparator; + bool m_suppressNewlines; bool m_newLine; bool m_valueWritten; bool m_error; @@ -117,6 +122,16 @@ inline void LuaTableWriter::write(char c) { write(&c, 1); } +/** + * Sets whether newlines should be suppressed. While newlines are suppressed, + * the writer will write out spaces instead of newlines. + */ +inline void LuaTableWriter::setSuppressNewlines(bool suppressNewlines) +{ m_suppressNewlines = suppressNewlines; } + +inline bool LuaTableWriter::suppressNewlines() const +{ return m_suppressNewlines; } + } // namespace Lua #endif // LUATABLEWRITER_H diff -Nru tiled-qt-0.7.1/src/plugins/plugins.pro tiled-qt-0.8.0/src/plugins/plugins.pro --- tiled-qt-0.7.1/src/plugins/plugins.pro 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/plugins.pro 2011-12-11 21:49:29.000000000 +0000 @@ -1,2 +1,7 @@ TEMPLATE = subdirs -SUBDIRS = droidcraft lua tmw tengine +SUBDIRS = flare \ + droidcraft \ + json \ + lua \ + tengine \ + tmw diff -Nru tiled-qt-0.7.1/src/plugins/tengine/tengineplugin.cpp tiled-qt-0.8.0/src/plugins/tengine/tengineplugin.cpp --- tiled-qt-0.7.1/src/plugins/tengine/tengineplugin.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/plugins/tengine/tengineplugin.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -46,7 +46,7 @@ using namespace Tiled; QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { mError = tr("Could not open file for writing."); return false; } @@ -54,7 +54,7 @@ // Write the header QString header = map->property("header"); - foreach (QString line, header.split("\\n")) { + foreach (const QString &line, header.split("\\n")) { out << line << endl; } @@ -109,18 +109,18 @@ } // Process the Object Layer } else if (objectLayer) { - foreach (MapObject *obj, objectLayer->objects()) { - if (floor(obj->y()) <= y and y <= floor(obj->y() + obj->height())) { - if (floor(obj->x()) <= x and x <= floor(obj->x() + obj->width())) { + foreach (const MapObject *obj, objectLayer->objects()) { + if (floor(obj->y()) <= y && y <= floor(obj->y() + obj->height())) { + if (floor(obj->x()) <= x && x <= floor(obj->x() + obj->width())) { // Check the Object Layer properties if either display or value was missing - if (not obj->property("display").isEmpty()) { + if (!obj->property("display").isEmpty()) { currentTile["display"] = obj->property("display"); - } else if (not objectLayer->property("display").isEmpty()) { + } else if (!objectLayer->property("display").isEmpty()) { currentTile["display"] = objectLayer->property("display"); } - if (not obj->property("value").isEmpty()) { + if (!obj->property("value").isEmpty()) { currentTile[layerKey] = obj->property("value"); - } else if (not objectLayer->property("value").isEmpty()) { + } else if (!objectLayer->property("value").isEmpty()) { currentTile[layerKey] = objectLayer->property("value"); } } @@ -129,7 +129,7 @@ } } // If the currentTile does not exist in the cache, add it - if (not cachedTiles.contains(currentTile["display"])) { + if (!cachedTiles.contains(currentTile["display"])) { cachedTiles[currentTile["display"]] = currentTile; // Otherwise check that it EXACTLY matches the cached one // and if not... @@ -147,7 +147,7 @@ } // If we haven't found a match then find a random display string // and cache it - if (not foundInCache) { + if (!foundInCache) { while (true) { // First try to use the ASCII characters if (asciiDisplay < ASCII_MAX) { @@ -159,7 +159,7 @@ overflowDisplay++; } currentTile["display"] = displayString; - if (not cachedTiles.contains(displayString)) { + if (!cachedTiles.contains(displayString)) { cachedTiles[displayString] = currentTile; break; } else if (currentTile == cachedTiles[currentTile["display"]]) { @@ -185,14 +185,14 @@ for (i = cachedTiles.constBegin(); i != cachedTiles.constEnd(); i++) { QString displayString = i.key(); // Only print the emptyTile definition if there were empty tiles - if (displayString == "?" and numEmptyTiles == 0) { + if (displayString == QLatin1String("?") && numEmptyTiles == 0) { continue; } // Need to escape " and \ characters - displayString.replace("\\", "\\\\"); - displayString.replace("\"", "\\\""); + displayString.replace(QLatin1Char('\\'), "\\\\"); + displayString.replace(QLatin1Char('"'), "\\\""); QString args = constructArgs(i.value(), propertyOrder); - if (not args.isEmpty()) { + if (!args.isEmpty()) { args = QString(", %1").arg(args); } out << QString("defineTile(\"%1\"%2)").arg(displayString, args) << endl; @@ -202,14 +202,14 @@ out << endl << "-- addSpot section" << endl; foreach (Layer *layer, map->layers()) { ObjectGroup *objectLayer = layer->asObjectGroup(); - if (objectLayer and objectLayer->name().startsWith("addspot", Qt::CaseInsensitive)) { - foreach (MapObject *obj, objectLayer->objects()) { + if (objectLayer && objectLayer->name().startsWith("addspot", Qt::CaseInsensitive)) { + foreach (const MapObject *obj, objectLayer->objects()) { QList propertyOrder; propertyOrder.append("type"); propertyOrder.append("subtype"); propertyOrder.append("additional"); QString args = constructArgs(obj->properties(), propertyOrder); - if (not args.isEmpty()) { + if (!args.isEmpty()) { args = QString(", %1").arg(args); } for (int y = floor(obj->y()); y <= floor(obj->y() + obj->height()); ++y) { @@ -225,14 +225,14 @@ out << endl << "-- addZone section" << endl; foreach (Layer *layer, map->layers()) { ObjectGroup *objectLayer = layer->asObjectGroup(); - if (objectLayer and objectLayer->name().startsWith("addzone", Qt::CaseInsensitive)) { + if (objectLayer && objectLayer->name().startsWith("addzone", Qt::CaseInsensitive)) { foreach (MapObject *obj, objectLayer->objects()) { QList propertyOrder; propertyOrder.append("type"); propertyOrder.append("subtype"); propertyOrder.append("additional"); QString args = constructArgs(obj->properties(), propertyOrder); - if (not args.isEmpty()) { + if (!args.isEmpty()) { args = QString(", %1").arg(args); } int top_left_x = floor(obj->x()); @@ -305,15 +305,15 @@ for (int i = propOrder.size() - 1; i >= 0; --i) { QString currentValue = props[propOrder[i]]; // Special handling of the "additional" property - if ((propOrder[i] == "additional") and currentValue.isEmpty()) { + if ((propOrder[i] == "additional") && currentValue.isEmpty()) { currentValue = constructAdditionalTable(props, propOrder); } - if (not argString.isEmpty()) { + if (!argString.isEmpty()) { if (currentValue.isEmpty()) { currentValue = "nil"; } argString = QString("%1, %2").arg(currentValue, argString); - } else if (not currentValue.isEmpty()) { + } else if (!currentValue.isEmpty()) { argString = currentValue; } } diff -Nru tiled-qt-0.7.1/src/tiled/aboutdialog.cpp tiled-qt-0.8.0/src/tiled/aboutdialog.cpp --- tiled-qt-0.7.1/src/tiled/aboutdialog.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/aboutdialog.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -28,11 +28,12 @@ AboutDialog::AboutDialog(QWidget *parent): QDialog(parent) { setupUi(this); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); const QString html = QCoreApplication::translate( "AboutDialog", "

Tiled Map Editor
Version %1

\n" - "

Copyright 2008-2010 Thorbjørn Lindeijer
(see the AUTHORS file for a full list of contributors)

\n" + "

Copyright 2008-2011 Thorbjørn Lindeijer
(see the AUTHORS file for a full list of contributors)

\n" "

You may modify and redistribute this program under the terms of the GPL (version 2 or later). " "A copy of the GPL is contained in the 'COPYING' file distributed with Tiled.

\n" "

http://www.mapeditor.org/

\n") diff -Nru tiled-qt-0.7.1/src/tiled/abstractobjecttool.cpp tiled-qt-0.8.0/src/tiled/abstractobjecttool.cpp --- tiled-qt-0.7.1/src/tiled/abstractobjecttool.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/abstractobjecttool.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -188,7 +188,7 @@ undoStack->beginMacro(tr("Duplicate %n Object(s)", "", objects.size())); QList clones; - foreach (MapObject *mapObject, objects) { + foreach (const MapObject *mapObject, objects) { MapObject *clone = mapObject->clone(); clones.append(clone); undoStack->push(new AddMapObject(mapDocument(), diff -Nru tiled-qt-0.7.1/src/tiled/automap.cpp tiled-qt-0.8.0/src/tiled/automap.cpp --- tiled-qt-0.7.1/src/tiled/automap.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/automap.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,1069 +0,0 @@ -/* - * automap.cpp - * Copyright 2010, Stefan Beller, stefanbeller@googlemail.com - * - * 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 "automap.h" - -#include "addremovelayer.h" -#include "addremovetileset.h" -#include "changeproperties.h" -#include "layer.h" -#include "layermodel.h" -#include "map.h" -#include "mapdocument.h" -#include "tile.h" -#include "tilelayer.h" -#include "tilepainter.h" -#include "tileset.h" -#include "tilesetmanager.h" -#include "tmxmapreader.h" - -#include -#include -#include -#include -#include - -using namespace Tiled; -using namespace Tiled::Internal; - -AutoMapper::AutoMapper(MapDocument *workingDocument, QString setlayer) - : mMapDocument(workingDocument) - , mMapWork(workingDocument ? workingDocument->map() : 0) - , mMapRules(0) - , mLayerRuleRegions(0) - , mSetLayer(setlayer) - , mSetLayerIndex(-1) -{ - -} - -AutoMapper::~AutoMapper() -{ - cleanUpRulesMap(); -} - -QSet AutoMapper::getTouchedLayers() const -{ - return mTouchedLayers; -} - -bool AutoMapper::prepareLoad(Map *rules, const QString &rulePath) -{ - mError.clear(); - - if (!setupMapDocumentLayers()) - return false; - - if (!setupRulesMap(rules, rulePath)) - return false; - - if (!setupRuleMapLayers()) - return false; - - if (!setupRulesUsedCheck()) - return false; - - if (!setupRuleList()) - return false; - - return true; -} - -bool AutoMapper::setupMapDocumentLayers() -{ - Q_ASSERT(mSetLayerIndex == -1); - mSetLayerIndex = mMapWork->indexOfLayer(mSetLayer); - - if (mSetLayerIndex == -1) - return false; - - return true; -} - -bool AutoMapper::setupRulesMap(Map *rules, const QString &rulePath) -{ - Q_ASSERT(!mMapRules); - - mMapRules = rules; - mRulePath = rulePath; - - QVariant p = rules->property(QLatin1String("DeleteTiles")); - - // defaulting to false, if there is no such property - mDeleteTiles = p.toBool(); - - QVariant q = rules->property(QLatin1String("AutomappingRadius")); - mAutoMappingRadius = q.toInt(); - - return true; -} - -bool AutoMapper::setupRuleMapLayers() -{ - Q_ASSERT(mLayerList.isEmpty()); - Q_ASSERT(!mLayerRuleRegions); - Q_ASSERT(mLayerRuleSets.isEmpty()); - Q_ASSERT(mLayerRuleNotSets.isEmpty()); - Q_ASSERT(mAddedTilesets.isEmpty()); - - QString prefix = QLatin1String("rule"); - - foreach (Layer *layer, mMapRules->layers()) { - if (TileLayer *tileLayer = layer->asTileLayer()) { - - if (!tileLayer->name().startsWith(prefix, Qt::CaseInsensitive)) - continue; - - // strip leading prefix, to make handling better - QString layername = tileLayer->name(); - layername.remove(0, prefix.length()); - - if (layername.startsWith( - QLatin1String("set"), Qt::CaseInsensitive)) { - mLayerRuleSets.append(tileLayer); - continue; - } - - if (layername.startsWith( - QLatin1String("notset"), Qt::CaseInsensitive)) { - mLayerRuleNotSets.append(tileLayer); - continue; - } - - if (layername.startsWith( - QLatin1String("regions"), Qt::CaseInsensitive)) { - mLayerRuleRegions = tileLayer; - continue; - } - - int pos = layername.indexOf(QLatin1Char('_')) + 1; - QString group = layername.left(pos) ; - - QString name = layername.right(layername.size() - pos); - - mTouchedLayers |= name; - - int t = mMapWork->indexOfLayer(name); - - QPair addPair(tileLayer, t); - - QList > *list = 0; - int j = 0; - - // put the list at the right location of mLayerList (a list of lists) - while ( !list && j != mLayerList.size() ) { - QString storedName = mLayerList.at(j)->at(0).first->name(); - // check if the group name is at the right position! index != -1 - // does not work, since the group name might be in the layer name - if (storedName.indexOf(group) == prefix.length()) - list = mLayerList.at(j); - j++; - } - - // now add the addPair data, which contains the current tilelayer - if (!list) { - list = new QList >(); - list->append(addPair); - mLayerList.append(list); - } else { - list->append(addPair); - } - } // if (asTileLayer) - } // foreach (layer) - - QString error; - - if (!mLayerRuleRegions) - error += tr("No ruleRegions layer found!") + QLatin1Char('\n'); - - if (mSetLayerIndex == -1) - error += tr("No set layers found!") + QLatin1Char('\n'); - - if (mLayerRuleSets.size() == 0) - error += tr("No ruleSet layer found!") + QLatin1Char('\n'); - - // no need to check for mLayerRuleNotSets.size() == 0 here. - // these layers are not necessary. - - if (!error.isEmpty()) { - error = mRulePath + QLatin1Char('\n') + error; - mError += error; - return false; - } - - return true; -} - -bool AutoMapper::setupRulesUsedCheck() -{ - TileLayer *setLayer = mMapWork->layerAt(mSetLayerIndex)->asTileLayer(); - QList tilesetWork = setLayer->usedTilesets().toList(); - foreach (TileLayer *tl, mLayerRuleSets) - foreach (Tileset *ts, tl->usedTilesets()) - if (ts->findSimilarTileset(tilesetWork)) - return true; - - foreach (TileLayer *tl, mLayerRuleNotSets) - foreach (Tileset *ts, tl->usedTilesets()) - if (ts->findSimilarTileset(tilesetWork)) - return true; - - return false; -} - -bool AutoMapper::setupRuleList() -{ - Q_ASSERT(mRules.isEmpty()); - Q_ASSERT(mLayerRuleRegions); - - for (int y = 1; y < mMapRules->height(); y++ ) { - for (int x = 1; x < mMapRules->width(); x++ ) { - if (!mLayerRuleRegions->cellAt(x, y).isEmpty() - && !isPartOfExistingRule(QPoint(x, y))) { - QRegion rule = createRule(x, y); - mRules << rule; - } - } - } - return true; -} - -bool AutoMapper::isPartOfExistingRule(const QPoint &p) const -{ - foreach (const QRegion ®ion, mRules) - if (region.contains(p)) - return true; - - return false; -} - -QRegion AutoMapper::createRule(int x, int y) const -{ - Q_ASSERT(mLayerRuleRegions); - QRegion ret(x, y, 1, 1); - QList addPoints; - const Cell &match = mLayerRuleRegions->cellAt(x, y); - addPoints.append(QPoint(x, y)); - - while (!addPoints.empty()) { - const QPoint current = addPoints.takeFirst(); - x = current.x(); - y = current.y(); - if (mLayerRuleRegions->contains(x - 1, y) - && mLayerRuleRegions->cellAt(x - 1, y) == match - && !ret.contains(QPoint(x - 1, y))) { - ret += QRegion(x - 1, y, 1, 1); - addPoints.append(QPoint(x - 1, y)); - } - if (mLayerRuleRegions->contains(x + 1, y) - && mLayerRuleRegions->cellAt(x + 1, y) == match - && !ret.contains(QPoint(x + 1, y))) { - ret += QRegion(x + 1, y, 1, 1); - addPoints.append(QPoint(x + 1, y)); - } - if (mLayerRuleRegions->contains(x, y - 1) - && mLayerRuleRegions->cellAt(x, y - 1) == match - && !ret.contains(QPoint(x, y - 1))) { - ret += QRegion(x, y - 1, 1, 1); - addPoints.append(QPoint(x, y - 1)); - } - if (mLayerRuleRegions->contains(x, y + 1) - && mLayerRuleRegions->cellAt(x, y + 1) == match - && !ret.contains(QPoint(x, y + 1))) { - ret += QRegion(x, y + 1, 1, 1); - addPoints.append(QPoint(x, y + 1)); - } - } - - return ret; -} - -bool AutoMapper::prepareAutoMap() -{ - if (!setupMissingLayers()) - return false; - - if (!setupTilesets(mMapRules, mMapWork)) - return false; - - return true; -} - -bool AutoMapper::setupMissingLayers() -{ - QList >* >::const_iterator j; - QList >::iterator i; - - for (j = mLayerList.constBegin(); j != mLayerList.constEnd(); ++j) { - for (i = (*j)->begin(); i != (*j)->end(); ++i) { - QString name = i->first->name(); - const int pos = name.indexOf(QLatin1Char('_')) + 1; - name = name.right(name.length()-pos); - - if (i->second >= mMapWork->layerCount() || - i->second == -1 || - name!= mMapWork->layerAt(i->second)->name()) { - - int index = mMapWork->indexOfLayer(name); - if (index == -1) { - index = mMapWork->layerCount(); - - TileLayer *t = new TileLayer(name, 0, 0, - mMapWork->width(), mMapWork->height()); - mMapDocument->undoStack()->push( - new AddLayer(mMapDocument, index, t)); - - mAddedTileLayers.append(name); - } - - QPair updatePair(i->first, index); - *i = updatePair; - } - } - } - - // check the set layer as well: - if (mSetLayerIndex >= mMapWork->layerCount() || - mSetLayer != mMapWork->layerAt(mSetLayerIndex)->name()) { - - mSetLayerIndex = mMapWork->indexOfLayer(mSetLayer); - - if (mSetLayerIndex == -1) - return false; - } - return true; -} - -/** - * this cannot just be replaced by MapDocument::unifyTileset(Map), - * because here mAddedTileset is modified - */ -bool AutoMapper::setupTilesets(Map *src, Map *dst) -{ - QList existingTilesets = dst->tilesets(); - - // Add tilesets that are not yet part of dst map - foreach (Tileset *tileset, src->tilesets()) { - if (existingTilesets.contains(tileset)) - continue; - - QUndoStack *undoStack = mMapDocument->undoStack(); - - Tileset *replacement = tileset->findSimilarTileset(existingTilesets); - if (!replacement) { - mAddedTilesets.append(tileset); - undoStack->push(new AddTileset(mMapDocument, tileset)); - continue; - } - - // Merge the tile properties - const int sharedTileCount = qMin(tileset->tileCount(), - replacement->tileCount()); - for (int i = 0; i < sharedTileCount; ++i) { - Tile *replacementTile = replacement->tileAt(i); - Properties properties = replacementTile->properties(); - properties.merge(tileset->tileAt(i)->properties()); - - undoStack->push(new ChangeProperties(tr("Tile"), - replacementTile, - properties)); - } - src->replaceTileset(tileset, replacement); - - TilesetManager *tilesetManager = TilesetManager::instance(); - tilesetManager->addReference(replacement); - tilesetManager->removeReference(tileset); - } - return true; -} - -void AutoMapper::autoMap(QRegion *where) -{ - // first resize the active area - if (mAutoMappingRadius) { - QRegion n; - foreach (const QRect &r, where->rects()) { - n += r.adjusted(- mAutoMappingRadius, - - mAutoMappingRadius, - + mAutoMappingRadius, - + mAutoMappingRadius); - } - *where += n; - } - - // delete all the relevant area, if the property "DeleteTiles" is set - if (mDeleteTiles) { - QList >* >::const_iterator j; - QList >::const_iterator i; - for (j = mLayerList.constBegin(); j != mLayerList.constEnd(); ++j) - for (i = (*j)->constBegin(); i != (*j)->constEnd(); ++i) - clearRegion(mMapWork->layerAt(i->second)->asTileLayer(), *where); - } - - // Increase the given region where the next automapper should work. - // This needs to be done, so you can rely on the order of the rules at all - // locations - QRegion ret; - foreach (const QRect &rect, where->rects()) - foreach (const QRegion &rule, mRules) { - // at the moment the parallel execution does not work yet - // TODO: make multithreading available! - // either by dividing the rules or the region to multiple threads - ret = ret.united(applyRule(rule, rect)); - } - *where = where->united(ret); -} - -void AutoMapper::clearRegion(TileLayer *dstLayer, const QRegion &where) -{ - TileLayer *setLayer = mMapWork->layerAt(mSetLayerIndex)->asTileLayer(); - QRegion region = where.intersected(dstLayer->bounds()); - foreach (QRect r, region.rects()) - for (int x = r.left(); x <= r.right(); x++) - for (int y = r.top(); y <= r.bottom(); y++) - if (setLayer->contains(x, y)) - if (!setLayer->cellAt(x, y).isEmpty()) - if (dstLayer->contains(x, y)) - dstLayer->setCell(x, y, Cell()); -} - -static bool compareLayerTo(TileLayer *l1, QVector listYes, - QVector listNo, const QRegion &r1, QPoint offset); - -QRect AutoMapper::applyRule(const QRegion &rule, const QRect &where) -{ - QRect ret; - - if (mLayerList.isEmpty()) - return ret; - - QRect rbr = rule.boundingRect(); - - // Since the rule itself is translated, we need to adjust the borders of the - // loops. Decrease the size at all sides by one: There must be at least one - // tile overlap to the rule. - const int min_x = where.left() - rbr.left() - rbr.width() + 1 ; - const int min_y = where.top() - rbr.top() - rbr.height() + 1; - - const int max_x = where.right() - rbr.left() + rbr.width() - 1; - const int max_y = where.bottom() - rbr.top() + rbr.height() - 1; - TileLayer *setLayer = mMapWork->layerAt(mSetLayerIndex)->asTileLayer(); - for (int y = min_y; y <= max_y; y++) - for (int x = min_x; x <= max_x; x++) - if (compareLayerTo(setLayer, mLayerRuleSets, - mLayerRuleNotSets, rule, QPoint(x, y))) { - int r = 0; - // choose by chance which group of rule_layers should be used: - if (mLayerList.size()>1) - r = qrand() % mLayerList.size(); - copyMapRegion(rule, QPoint(x, y), *mLayerList.at(r)); - ret = ret.united(rbr.translated(QPoint(x, y))); - } - - return ret; -} - -/** - * Returns a list of all cells which can be found within all tile layers - * within the given region. - */ -static QVector cellsInRegion(QVector list, const QRegion &r) -{ - QVector cells; - foreach (TileLayer *l, list) { - foreach (const QRect &rect, r.rects()) { - for (int x = rect.left(); x <= rect.right(); x++) { - for (int y = rect.top(); y <= rect.bottom(); y++) { - const Cell &cell = l->cellAt(x, y); - if (!cells.contains(cell)) - cells.append(cell); - } - } - } - } - return cells; -} - -/** - * This function is one of the core functions for understanding the - * automapping. - * In this function a certain region (of the set layer) is compared to - * several other layers (ruleSet and ruleNotSet). - * This comparision will determine if a rule of automapping matches, - * so if this rule is applied at this region given - * by a QRegion and Offset given by a QPoint. - * - * This compares the tile layer l1 (set layer) to several others given - * in the QList listYes (ruleSet) and OList listNo (ruleNotSet). - * The tile layer l1 is examined at QRegion r1 + offset - * The tile layers within listYes and listNo are examined at QRegion r1. - * - * Basically all matches between l1 and a layer of listYes are considered - * good, while all matches between l1 and listNo are considered bad and lead to - * canceling the comparison, returning false. - * - * The comparison is done for each position within the QRegion r1. - * If all positions of the region are considered "good" return true. - * - * Now there are several cases to distinguish: - * - l1 is 0: - * obviously there should be no automapping. - * So here no rule should be applied. return false - * - l1 is not 0: - * - both listYes and listNo are empty: - * should not happen, because with that configuration, absolutly - * no condition is given. - * return false, assuming this is an errornous rule being applied - * - * - both listYes and listNo are not empty: - * When comparing a tile at a certain position of TileLayer l1 - * to all available tiles in listYes, there must be at least - * one layer, in which there is a match of tiles of l1 and listYes - * to consider this position good. - * In listNo there must not be a match to consider this position - * good. - * If there are no tiles within all available tiles within all layers - * of one list, all tiles in l1 are considered good, - * while inspecting this list. - * All available tiles are all tiles within the whole rule region in - * all tile layers of the list. - * - * - either of both lists is empty - * When comparing a certain position of TileLayer l1 to all Tiles - * at the corresponding position this can happen: - * A tile of l1 matches a tile of a layer in the list. Then this - * is considered as good, if the layer is from the listYes. - * Otherwise it is considered bad. - * - * Exception, when having only the listYes: - * if at the examined position there are no tiles within all Layers - * of the listYes, all tiles except all used tiles within - * the layers of that list are considered good. - * - * This exception was added to have a better functionality - * (need of less layers.) - * It was not added to the case, when having only listNo layers to - * avoid total symmetrie between those lists. - * - * If all positions are considered good, return true. - * return false otherwise. - * - * @return bool, if the tile layer matches the given list of layers. - */ -static bool compareLayerTo(TileLayer *l1, QVector listYes, - QVector listNo, const QRegion &r1, QPoint offset) -{ - if (listYes.size() == 0 && listNo.size() == 0) - return false; - - QVector cells; - if (listYes.size() == 0) - cells = cellsInRegion(listNo, r1); - if (listNo.size() == 0) - cells = cellsInRegion(listYes, r1); - - foreach (QRect rect, r1.rects()) { - for (int x = rect.left(); x <= rect.right(); x++) { - for (int y = rect.top(); y <= rect.bottom(); y++) { - // this is only used in the case where only one list has layers - // it is needed for the exception mentioned above - bool ruleDefinedListYes = false; - - bool matchListYes = false; - bool matchListNo = false; - - if (!l1->contains(x + offset.x(), y + offset.y())) - return false; - - const Cell &c1 = l1->cellAt(x + offset.x(), y + offset.y()); - - // when there is no tile in l1 (= set layer), - // there should be no rule at all - if (c1.isEmpty()) - return false; - - // ruleDefined will be set when there is a tile in at least - // one layer. if there is a tile in at least one layer, only - // the given tiles in the different listYes layers are valid. - // if there is given no tile at all in the listYes layers, - // consider all tiles valid. - - foreach (TileLayer *l2, listYes) { - - if (!l2->contains(x, y)) - return false; - - const Cell &c2 = l2->cellAt(x, y); - if (!c2.isEmpty()) - ruleDefinedListYes = true; - - if (!c2.isEmpty() && c1 == c2) - matchListYes = true; - } - foreach (TileLayer *l2, listNo) { - - if (!l2->contains(x, y)) - return false; - - const Cell &c2 = l2->cellAt(x, y); - - if (!c2.isEmpty() && c1 == c2) - matchListNo = true; - } - - // when there are only layers in the listNo - // check only if these layers are unmatched - // no need to check explicitly the exception in this case. - if (listYes.size() == 0 ) { - if (matchListNo) - return false; - else - continue; - } - // when there are only layers in the listYes - // check if these layers are matched, or if the exception works - if (listNo.size() == 0 ) { - if (matchListYes) - continue; - if (!ruleDefinedListYes && !cells.contains(c1)) - continue; - return false; - } - - // there are layers in both lists: - // no need to consider ruleDefinedListXXX - if ((matchListYes || !ruleDefinedListYes) && !matchListNo) - continue; - else - return false; - } - } - } - return true; -} - -void AutoMapper::copyMapRegion(const QRegion ®ion, QPoint offset, - const QList< QPair > &layerTranslation) -{ - QList< QPair >::const_iterator lr_i; - for (lr_i = layerTranslation.begin(); - lr_i != layerTranslation.end(); - ++lr_i) { - foreach (QRect rect, region.rects()) { - if (lr_i->second != -1) - copyRegion(lr_i->first, - rect.x(), rect.y(), - rect.width(), rect.height(), - mMapWork->layerAt(lr_i->second)->asTileLayer(), - rect.x() + offset.x(), rect.y() + offset.y()); - } - } -} - -void AutoMapper::copyRegion(TileLayer *srcLayer, int srcX, int srcY, - int width, int height, - TileLayer *dstLayer, int dstX, int dstY) -{ - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - const Cell &cell = srcLayer->cellAt(srcX + x, srcY + y); - if (!cell.isEmpty()) { - // this is without graphics update, it's done afterwards for all - dstLayer->setCell(dstX + x, dstY + y, cell); - } - } - } -} - -void AutoMapper::cleanAll() -{ - cleanTilesets(); -} - -void AutoMapper::cleanTilesets() -{ - foreach (Tileset *t, mAddedTilesets) { - if (mMapWork->isTilesetUsed(t)) - continue; - - const int layerIndex = mMapWork->indexOfTileset(t); - if (layerIndex != -1) { - QUndoCommand *cmd = new RemoveTileset(mMapDocument, layerIndex, t); - mMapDocument->undoStack()->push(cmd); - } - } - mAddedTilesets.clear(); -} - -void AutoMapper::cleanUpRulesMap() -{ - cleanTilesets(); - - // mMapRules can be empty, when in prepareLoad the very first stages fail. - if (!mMapRules) - return; - - TilesetManager *tilesetManager = TilesetManager::instance(); - tilesetManager->removeReferences(mMapRules->tilesets()); - - delete mMapRules; - mMapRules = 0; - - cleanUpRuleMapLayers(); - mRules.clear(); -} - -void AutoMapper::cleanUpRuleMapLayers() -{ - foreach (const QString &t, mAddedTileLayers) { - const int layerindex = mMapWork->indexOfLayer(t); - if (layerindex != -1) { - TileLayer *t = mMapWork->layerAt(layerindex)->asTileLayer(); - if (t->isEmpty()) { - mMapDocument->undoStack()->push( - new RemoveLayer(mMapDocument, layerindex)); - } - } - } - - QList >* >::const_iterator j; - for (j = mLayerList.constBegin(); j != mLayerList.constEnd(); ++j) - delete (*j); - - mLayerList.clear(); - // do not delete mLayerRuleRegions, it is owned by the map mapWork, which - // cares for it - mLayerRuleRegions = 0; - mLayerRuleSets.clear(); - mLayerRuleNotSets.clear(); -} - -AutoMapperWrapper::AutoMapperWrapper(MapDocument *mapDocument, QVector autoMapper, QRegion *where) -{ - mMapDocument = mapDocument; - Map *map = mMapDocument->map(); - - QSet touchedlayers; - foreach (AutoMapper *a, autoMapper) { - a->prepareAutoMap(); - touchedlayers|= a->getTouchedLayers(); - } - foreach (const QString &layerName, touchedlayers) { - const int layerindex = map->indexOfLayer(layerName); - Q_ASSERT(layerindex != -1); - mLayersBefore << static_cast(map->layerAt(layerindex)->clone()); - } - - foreach (AutoMapper *a, autoMapper) { - a->autoMap(where); - } - - foreach (const QString &layerName, touchedlayers) { - const int layerindex = map->indexOfLayer(layerName); - // layerindex exists, because AutoMapper is still alive, dont check - Q_ASSERT(layerindex != -1); - mLayersAfter << static_cast(map->layerAt(layerindex)->clone()); - } - // reduce memory usage by saving only diffs - Q_ASSERT(mLayersAfter.size() == mLayersBefore.size()); - for (int i = 0; i < mLayersAfter.size(); i++) { - TileLayer *before = mLayersBefore.at(i); - TileLayer *after = mLayersAfter.at(i); - QRect diffRegion = before->computeDiffRegion(after).boundingRect(); - - TileLayer *before1 = before->copy(diffRegion); - TileLayer *after1 = after->copy(diffRegion); - - before1->setPosition(diffRegion.topLeft()); - after1->setPosition(diffRegion.topLeft()); - before1->setName(before->name()); - after1->setName(after->name()); - mLayersBefore.replace(i, before1); - mLayersAfter.replace(i, after1); - - delete before; - delete after; - } - - foreach (AutoMapper *a, autoMapper) { - a->cleanAll(); - } -} - -AutoMapperWrapper::~AutoMapperWrapper() -{ - QVector::iterator i; - for (i = mLayersAfter.begin(); i != mLayersAfter.end(); ++i) - delete *i; - for (i = mLayersBefore.begin(); i != mLayersBefore.end(); ++i) - delete *i; -} - -void AutoMapperWrapper::undo() -{ - Map *map = mMapDocument->map(); - QVector::iterator i; - for (i = mLayersBefore.begin(); i != mLayersBefore.end(); ++i) { - const int layerindex = map->indexOfLayer((*i)->name()); - if (layerindex != -1) - patchLayer(layerindex, *i); - } -} - -void AutoMapperWrapper::redo() -{ - Map *map = mMapDocument->map(); - QVector::iterator i; - for (i = mLayersAfter.begin(); i != mLayersAfter.end(); ++i) { - const int layerindex = (map->indexOfLayer((*i)->name())); - if (layerindex != -1) - patchLayer(layerindex, *i); - } - -} - -void AutoMapperWrapper::patchLayer(int layerIndex, TileLayer *layer) -{ - Map *map = mMapDocument->map(); - QRect b = layer->bounds(); - - Q_ASSERT(map->layerAt(layerIndex)->asTileLayer()); - TileLayer *t = static_cast(map->layerAt(layerIndex)); - - t->setCells(b.left() - t->x(), b.top() - t->y(), layer, - b.translated(-t->position())); - mMapDocument->emitRegionChanged(b); -} - -AutomaticMappingManager *AutomaticMappingManager::mInstance = 0; - -AutomaticMappingManager::AutomaticMappingManager(QObject *parent) - : QObject(parent) - , mMapDocument(0) - , mLoaded(false) - , mWatcher(new QFileSystemWatcher(this)) -{ - connect(mWatcher, SIGNAL(fileChanged(QString)), - this, SLOT(fileChanged(QString))); - mChangedFilesTimer.setInterval(100); - mChangedFilesTimer.setSingleShot(true); - connect(&mChangedFilesTimer, SIGNAL(timeout()), - this, SLOT(fileChangedTimeout())); - // this should be stored in the project file later on. - // now just default to the value we always had. - mSetLayer = QLatin1String("set"); -} - -AutomaticMappingManager::~AutomaticMappingManager() -{ - cleanUp(); - delete mWatcher; -} - -AutomaticMappingManager *AutomaticMappingManager::instance() -{ - if (!mInstance) - mInstance = new AutomaticMappingManager(0); - - return mInstance; -} - -void AutomaticMappingManager::deleteInstance() -{ - delete mInstance; - mInstance = 0; -} - -void AutomaticMappingManager::automap() -{ - if (!mMapDocument) - return; - - Map *map = mMapDocument->map(); - int w = map->width(); - int h = map->height(); - int l = map->indexOfLayer(mSetLayer); - if (l != -1) - automap(QRect(0, 0, w, h), map->layerAt(l)); - else - mError = tr("No set layer found!") + QLatin1Char('\n'); -} - -void AutomaticMappingManager::automap(QRegion where, Layer *l) -{ - if (!mMapDocument) - return; - - if (l->name() != mSetLayer) - return; - - if (!mLoaded) { - const QString mapPath = QFileInfo(mMapDocument->fileName()).path(); - const QString rulesFileName = mapPath + QLatin1String("/rules.txt"); - if (loadFile(rulesFileName)) - mLoaded = true; - } - - Map *map = mMapDocument->map(); - - QString layer = map->layerAt(mMapDocument->currentLayerIndex())->name(); - - // use a pointer to the region, so each automapper can manipulate it and the - // following automappers do see the impact - QRegion *passedRegion = new QRegion(where); - - QUndoStack *undoStack = mMapDocument->undoStack(); - undoStack->beginMacro(tr("Apply AutoMap rules")); - AutoMapperWrapper *aw = new AutoMapperWrapper(mMapDocument, mAutoMappers, passedRegion); - undoStack->push(aw); - undoStack->endMacro(); - - mMapDocument->emitRegionChanged(*passedRegion); - delete passedRegion; - mMapDocument->setCurrentLayerIndex(map->indexOfLayer(layer)); -} - -bool AutomaticMappingManager::loadFile(const QString &filePath) -{ - mError.clear(); - bool ret = true; - const QString absPath = QFileInfo(filePath).path(); - QFile rulesFile(filePath); - - if (!rulesFile.exists()) { - mError += tr("No rules file found at:\n%1").arg(filePath) - + QLatin1Char('\n'); - return false; - } - if (!rulesFile.open(QIODevice::ReadOnly)) { - mError += tr("Error opening rules file:\n%1").arg(filePath) - + QLatin1Char('\n'); - return false; - } - - QTextStream in(&rulesFile); - QString line = in.readLine(); - - for (; !line.isNull(); line = in.readLine()) { - QString rulePath = line.trimmed(); - if (rulePath.isEmpty() - || rulePath.startsWith(QLatin1Char('#')) - || rulePath.startsWith(QLatin1String("//"))) - continue; - - if (QFileInfo(rulePath).isRelative()) - rulePath = absPath + QLatin1Char('/') + rulePath; - - if (!QFileInfo(rulePath).exists()) { - mError += tr("File not found:\n%1").arg(rulePath) + QLatin1Char('\n'); - ret = false; - continue; - } - if (rulePath.endsWith(QLatin1String(".tmx"), Qt::CaseInsensitive)){ - TmxMapReader mapReader; - - Map *rules = mapReader.read(rulePath); - - TilesetManager *tilesetManager = TilesetManager::instance(); - tilesetManager->addReferences(rules->tilesets()); - - if (!rules) { - mError += tr("Opening rules map failed:\n%1").arg( - mapReader.errorString()) + QLatin1Char('\n'); - ret = false; - continue; - } - AutoMapper *autoMapper; - autoMapper = new AutoMapper(mMapDocument, mSetLayer); - - if (autoMapper->prepareLoad(rules, rulePath)) - mAutoMappers.append(autoMapper); - else - delete autoMapper; - } - if (rulePath.endsWith(QLatin1String(".txt"), Qt::CaseInsensitive)){ - if (!loadFile(rulePath)) - ret = false; - } - } - return ret; -} - -void AutomaticMappingManager::setMapDocument(MapDocument *mapDocument) -{ - cleanUp(); - if (mMapDocument) - mMapDocument->disconnect(this); - - mMapDocument = mapDocument; - - if (mMapDocument) - connect(mMapDocument, SIGNAL(regionEdited(QRegion,Layer*)), - this, SLOT(automap(QRegion,Layer*))); - mLoaded = false; - -} - -void AutomaticMappingManager::cleanUp() -{ - foreach (AutoMapper *autoMapper, mAutoMappers) { - delete autoMapper; - } - mAutoMappers.clear(); -} - -void AutomaticMappingManager::fileChanged(const QString &path) -{ - /* - * Use a one-shot timer and wait a little, to be sure there are no - * further modifications within very short time. - */ - if (!mChangedFiles.contains(path)) { - mChangedFiles.insert(path); - mChangedFilesTimer.start(); - } -} - -void AutomaticMappingManager::fileChangedTimeout() -{ - for (int i = 0; i != mAutoMappers.size(); i++) { - AutoMapper *am = mAutoMappers.at(i); - QString fileName = am->ruleSetPath(); - if (mChangedFiles.contains(fileName)) { - delete am; - - TmxMapReader mapReader; - Map *rules = mapReader.read(fileName); - if (!rules) { - mAutoMappers.remove(i); - continue; - } - - AutoMapper *autoMapper = new AutoMapper(mMapDocument, mSetLayer); - if (autoMapper->prepareLoad(rules, fileName)) { - mAutoMappers.replace(i, autoMapper); - } else { - delete autoMapper; - mAutoMappers.remove(i); - } - } - } - mChangedFiles.clear(); -} diff -Nru tiled-qt-0.7.1/src/tiled/automap.h tiled-qt-0.8.0/src/tiled/automap.h --- tiled-qt-0.7.1/src/tiled/automap.h 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/automap.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,489 +0,0 @@ -/* - * automap.h - * Copyright 2010, Stefan Beller, stefanbeller@googlemail.com - * - * 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 . - */ - -#ifndef AUTOMAP_H -#define AUTOMAP_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class QFileSystemWatcher; -class QObject; - -namespace Tiled { - -class Layer; -class Map; -class TileLayer; -class Tileset; - -namespace Internal { - -class MapDocument; - -/** - * This class does all the work for the automapping feature. - * basically it can do the following: - * - check the rules map for rules and store them - * - compare TileLayers (i. e. check if/where a certain rule must be applied) - * - copy regions of Maps (multiple Layers, the layerlist is a - * lookup-table for matching the Layers) - */ -class AutoMapper : public QObject -{ - Q_OBJECT - -public: - /** - * Constructs a AutoMapper. - * - * @param workingDocument: the map to work on. - */ - AutoMapper(MapDocument *workingDocument, QString setlayer); - ~AutoMapper(); - - MapDocument *mapDocument() const { return mMapDocument; } - - QString ruleSetPath() const { return mRulePath; } - - /** - * This sets up some internal data structures, which do not change, - * so it is needed only when loading the rules map. - */ - bool prepareLoad(Map *rules, const QString &rulePath); - - /** - * Call prepareLoad first! Returns a set of strings describing the layers, - * which are likely touched. Actually this function returns all layers, - * which could be touched, when considering only the given layers of the - * rule map. - */ - QSet getTouchedLayers() const; - - /** - * This needs to be called directly before the autoMap call. - * It sets up some data structures which change rapidly, so it is quite - * painful to keep these datastructures up to date all time. - */ - bool prepareAutoMap(); - - /** - * Here is done all the automapping. - */ - void autoMap(QRegion *where); - - /** - * This cleans all datastructures, which are setup via prepareAutoMap, - * so the auto mapper becomes ready for its next automatic mapping. - */ - void cleanAll(); - - QString errorString() const { return mError; } - -private: - /** - * Calls all setup-functions in the right order needed for processing - * a new rules file. - * - * param rulePath is only used to have better error message descriptions. - * - * @return returns true when anything is ok, false when errors occured. - * (in that case will be a msg box anyway) - */ - bool setupRulesMap(Map *rules, const QString &rulePath); - - void cleanUpRulesMap(); - - /** - * Sets up the set layer in the mapDocument, which is used for automapping - * @return returns true when anything is ok, false when errors occured. - * (in that case will be a msg box anyway) - */ - bool setupMapDocumentLayers(); - - /** - * Searches the rules layer for regions and stores these in \a rules. - * @return returns true when anything is ok, false when errors occured. - * (in that case will be a msg box anyway) - */ - bool setupRuleList(); - - /** - * Sets up the layers in the rules map, which are used for automapping. - * The layers are detected and put in the internal data structures - * @return returns true when anything is ok, false when errors occured. - * (in that case will be a msg box anyway) - */ - bool setupRuleMapLayers(); - - /** - * Checks if the layers setup as in setupRuleMapLayers are still right. - * If it's not right, correct them. - * @return returns true if everything went fine. false is returned when - * no set layer was found - */ - bool setupMissingLayers(); - - /** - * sets up the tilesets which are used in automapping. - * @return returns true when anything is ok, false when errors occured. - * (in that case will be a msg box anyway) - */ - bool setupTilesets(Map *src, Map *dst); - - /** - * sets all tiles to 0 in the specified rectangle of the given tile layer. - */ - void clearRegion(TileLayer *dstLayer, const QRegion &where); - - /** - * This copies all Tiles from TileLayer src to TileLayer dst - * - * In src the Tiles are taken from the rectangle given by - * src_x, src_y, width and height. - * In dst they get copied to a rectangle given by - * dst_x, dst_y, width, height . - * if there is no tile in src TileLayer, there will nothing be copied, - * so the maybe existing tile in dst will not be overwritten. - * - */ - void copyRegion(TileLayer *src_lr, int src_x, int src_y, - int width, int height, TileLayer *dst_lr, - int dst_x, int dst_y); - - /** - * This copies multiple TileLayers from one map to another. - * To handle multiple Layers, LayerTranslation is a List of Pairs. - * The first of the QPair is the src and it gets copied to the Layer - * in second of QPair. - * Only the region \a region is considered for copying. - * In the destination it will come to the region translated by Offset. - */ - void copyMapRegion(const QRegion ®ion, QPoint Offset, - const QList< QPair > &LayerTranslation); - - /** - * This goes through all the positions of the mMapWork and checks if - * there fits the rule given by the region in mMapRuleSet. - * if there is a match all Layers are copied to mMapWork. - * @param rule: the region which should be compared to all positions - * of mMapWork - * @return where: an rectangle where the rule actually got applied - */ - QRect applyRule(const QRegion &rule, const QRect &where); - - /** - * This returns whether the given point \a p is part of an existing rule. - */ - bool isPartOfExistingRule(const QPoint &p) const; - - /** - * This creates a rule from a given point. - * So it will be checked, which regions are coherent to this point - * and all these regions will be treated as a new rule, - * which will be returned. To check what is coherent, a - * breadth first search will be performed, whereas each Tile is a node, - * and the 4 coherent tiles are connected to this node. - */ - QRegion createRule(int x, int y) const; - - /** - * cleans up the data structes filled by setupRuleMapLayers(), - * so the next rule can be processed. - */ - void cleanUpRuleMapLayers(); - - /** - * cleans up the data structes filled by setupTilesets(), - * so the next rule can be processed. - */ - void cleanTilesets(); - - /** - * checks if this the rules from the given rules map could be used anyway - * by comparing the used tilesets of the set layer and ruleset layer. - */ - bool setupRulesUsedCheck(); - - /** - * where to work in - */ - MapDocument *mMapDocument; - - /** - * the same as mMapDocument->map() - */ - Map *mMapWork; - - /** - * map containing the rules, usually different than mMapWork - */ - Map *mMapRules; - - /** - * This contains all added tilesets as pointers. - * if rules use Tilesets which are not in the mMapWork they are added. - * keep track of them, because we need to delete them afterwards, - * when they still are unused - * they will be added while setupTilesets(). - * they will be deleted at Destructor of AutoMapper. - */ - QVector mAddedTilesets; - - /** - * description see: mAddedTilesets, just described by Strings - */ - QList mAddedTileLayers; - - /** - * RuleRegions is the layer where the regions are defined. - */ - TileLayer *mLayerRuleRegions; - - /** - * mLayerSet is compared at each tile if it matches any Tile within the - * mLayerRuleSets list - * it must not match with any Tile to mLayerRuleNotSets - */ - QVector mLayerRuleSets; - QVector mLayerRuleNotSets; - - /** - * This stores the name of the layer, which is used in the working map to - * setup the automapper. - * Until this variable was introduced it was called "set" (hardcoded) - */ - QString mSetLayer; - - /** - * This is the index of the tile layer, which is used in the working map for - * automapping. - * So if anything is correct mMapWork->layerAt(mLayerSet)->name() - * equals mSetLayer. - */ - int mSetLayerIndex; - - /** - * List of Regions in mMapRules to know where the rules are - */ - QList mRules; - - /** - * The inner List of Tuples with layers is needed for translating - * tile layers from mMapRules to mMapWork. - * - * QPairs first entry is the pointer to the layer in the rulemap. The - * pointer to the layer within the working map is not hardwired, but the - * position in the layerlist, where it was found the last time. - * This loosely bound pointer ensures we will get the right layer, since we - * need to check before anyway, and it is still fast. - * - * The outer list is used to hold different translation tables - * => one of the inner lists is chosen by chance, so randomness is available - */ - QList >* > mLayerList; - - /** - * store the name of the processed rules file, to have detailed - * error messages available - */ - QString mRulePath; - - /** - * determines if all tiles in all touched layers should be deleted first. - */ - bool mDeleteTiles; - - /** - * This variable determines, how many overlapping tiles should be used. - * The bigger the more area is remapped at an automapping operation. - * This can lead to higher latency, but provides a better behavior on - * interactive automapping. - * It defaults to zero. - */ - int mAutoMappingRadius; - - QSet mTouchedLayers; - - QString mError; -}; - -/** - * This is a wrapper class for the AutoMapper class. - * Here in this class only undo/redo functionality for one rulemap - * is provided. - * This class will take a snapshot of the layers before and after the - * automapping is done. In between instances of AutoMapper are doing the work. - */ - -class AutoMapperWrapper : public QUndoCommand -{ -public: - AutoMapperWrapper(MapDocument *mapDocument, QVector autoMapper, - QRegion *where); - ~AutoMapperWrapper(); - - void undo(); - void redo(); - -private: - void patchLayer(int layerIndex, TileLayer *layer); - - MapDocument *mMapDocument; - QVector mLayersAfter; - QVector mLayersBefore; -}; - -/** - * This class is a superior class to the AutoMapper and AutoMapperWrapper class. - * It uses these classes to do the whole automapping process. - */ -class AutomaticMappingManager: public QObject -{ - Q_OBJECT - -public: - /** - * Requests the AutomaticMapping manager. When the manager doesn't exist - * yet, it will be created. - */ - static AutomaticMappingManager *instance(); - - /** - * Deletes the AutomaticMapping manager instance, when it exists. - */ - static void deleteInstance(); - - /** - * This triggers an automapping on the whole current map document. - */ - void automap(); - - void setMapDocument(MapDocument *mapDocument); - - QString errorString() const { return mError; } - -public slots: - /** - * This sets up new AutoMapperWrappers, which trigger the automapping. - * The region 'where' describes where only the automapping takes place. - * This is a signal so it can directly be connected to the regionEdited - * signal of map documents. - */ - void automap(QRegion where, Layer *layer); - -private slots: - /** - * connected to the QFileWatcher, which monitors all rules files for changes - */ - void fileChanged(const QString &path); - - /** - * This is connected to the timer, which fires once after the files changed. - */ - void fileChangedTimeout(); - -private: - Q_DISABLE_COPY(AutomaticMappingManager) - - /** - * Constructor. Only used by the AutomaticMapping manager itself. - */ - AutomaticMappingManager(QObject *parent); - - ~AutomaticMappingManager(); - - static AutomaticMappingManager *mInstance; - - /** - * This function parses a rules file. - * For each path which is a rule, (fileextension is tmx) an AutoMapper - * object is setup. - * - * If a fileextension is txt, this file will be opened and searched for - * rules again. - * - * @return if the loading was successful: return true if it suceeded. - */ - bool loadFile(const QString &filePath); - - /** - * deletes all its data structures - */ - void cleanUp(); - - /** - * The current map document. - */ - MapDocument *mMapDocument; - - /** - * For each new file of rules a new AutoMapper is setup. In this vector we - * can store all of the AutoMappers in order. - */ - QVector mAutoMappers; - - /** - * This tells you if the rules for the current map document were already - * loaded. - */ - bool mLoaded; - - /** - * The all used rulefiles are monitored by this object, so in case of - * external changes it will automatically reloaded. - */ - QFileSystemWatcher *mWatcher; - - /** - * All external changed files will be put in here, so these will be loaded - * altogether when the timer expires. - */ - QSet mChangedFiles; - - /** - * This timer is started when the first file was modified. The files are - * actually reloaded after this timer expires, so just in case the files - * get modified within short time delays (some editors do so), it will - * wait until all is over an reload everything at the timeout. - */ - QTimer mChangedFilesTimer; - - QString mError; - - /** - * This stores the name of the layer, which is used in the working map to - * setup the automapper. - * Until this variable was introduced it was called "set" (hardcoded) - */ - QString mSetLayer; -}; - -} // namespace Internal -} // namespace Tiled - -#endif // AUTOMAP_H diff -Nru tiled-qt-0.7.1/src/tiled/automapper.cpp tiled-qt-0.8.0/src/tiled/automapper.cpp --- tiled-qt-0.7.1/src/tiled/automapper.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/automapper.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,839 @@ +/* + * automapper.cpp + * Copyright 2010-2011, Stefan Beller, stefanbeller@googlemail.com + * + * 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 "automapper.h" + +#include "addremovelayer.h" +#include "addremovetileset.h" +#include "changeproperties.h" +#include "layermodel.h" +#include "map.h" +#include "mapdocument.h" +#include "tile.h" +#include "tilelayer.h" +#include "tileset.h" +#include "tilesetmanager.h" + +using namespace Tiled; +using namespace Tiled::Internal; + +/* + * About the order of the methods in this file. + * The Automapper class has 3 bigger public functions, that is + * prepareLoad(), prepareAutoMap() and autoMap(). + * These three functions make use of lots of different private methods, which + * are put directly below each of these functions. + */ + +AutoMapper::AutoMapper(MapDocument *workingDocument, QString setlayer) + : mMapDocument(workingDocument) + , mMapWork(workingDocument ? workingDocument->map() : 0) + , mMapRules(0) + , mLayerRuleRegions(0) + , mSetLayer(setlayer) + , mSetLayerIndex(-1) + , mDeleteTiles(false) + , mAutoMappingRadius(0) + , mNoOverlappingRules(false) +{ +} + +AutoMapper::~AutoMapper() +{ + cleanUpRulesMap(); +} + +QSet AutoMapper::getTouchedLayers() const +{ + return mTouchedLayers.toSet(); +} + +bool AutoMapper::prepareLoad(Map *rules, const QString &rulePath) +{ + mError.clear(); + mWarning.clear(); + + if (!setupMapDocumentLayers()) + return false; + + if (!setupRulesMap(rules, rulePath)) + return false; + + if (!setupRuleMapTileLayers()) + return false; + + if (!setupRulesUsedCheck()) + return false; + + if (!setupRuleList()) + return false; + + return true; +} + +bool AutoMapper::setupMapDocumentLayers() +{ + Q_ASSERT(mSetLayerIndex == -1); + mSetLayerIndex = mMapWork->indexOfLayer(mSetLayer); + + if (mSetLayerIndex == -1) + return false; + + return true; +} + +bool AutoMapper::setupRulesMap(Map *rules, const QString &rulePath) +{ + Q_ASSERT(!mMapRules); + + mMapRules = rules; + mRulePath = rulePath; + + Properties properties = rules->properties(); + foreach (QString key, properties.keys()) { + QVariant value = properties.value(key); + bool raiseWarning = true; + if (key.toLower() == QLatin1String("deletetiles")) { + if (value.canConvert(QVariant::Bool)) { + mDeleteTiles = value.toBool(); + raiseWarning = false; + } + } else if (key.toLower() == QLatin1String("automappingradius")) { + if (value.canConvert(QVariant::Int)) { + mAutoMappingRadius = value.toInt(); + raiseWarning = false; + } + } else if (key.toLower() == QLatin1String("nooverlappingrules")) { + if (value.canConvert(QVariant::Bool)) { + mNoOverlappingRules = value.toBool(); + raiseWarning = false; + } + } + if (raiseWarning) + mWarning += tr("%1: Property %2 = %3 does not make sense. " + "Ignoring this property.") + .arg(rulePath, key, value.toString()) + QLatin1Char('\n'); + } + return true; +} + +bool AutoMapper::setupRuleMapTileLayers() +{ + Q_ASSERT(mLayerList.isEmpty()); + Q_ASSERT(!mLayerRuleRegions); + Q_ASSERT(mLayerRuleSets.isEmpty()); + Q_ASSERT(mLayerRuleNotSets.isEmpty()); + Q_ASSERT(mAddedTilesets.isEmpty()); + + QString prefix = QLatin1String("rule"); + + foreach (Layer *layer, mMapRules->layers()) { + TileLayer *tileLayer = layer->asTileLayer(); + if (!tileLayer) + continue; + + if (!tileLayer->name().startsWith(prefix, Qt::CaseInsensitive)) { + mWarning += tr("Layer %1 found in automapping rules." + "Did you mean %2_%1? Ignoring that layer!") + .arg(tileLayer->name(), prefix) + QLatin1Char('\n'); + continue; + } + + // strip leading prefix, to make handling better + QString layername = tileLayer->name(); + layername.remove(0, prefix.length()); + + if (layername.startsWith(QLatin1String("set"), + Qt::CaseInsensitive)) { + mLayerRuleSets.append(tileLayer); + continue; + } + + if (layername.startsWith(QLatin1String("notset"), + Qt::CaseInsensitive)) { + mLayerRuleNotSets.append(tileLayer); + continue; + } + + if (layername.startsWith(QLatin1String("regions"), + Qt::CaseInsensitive)) { + mLayerRuleRegions = tileLayer; + continue; + } + + int nameStartPosition = layername.indexOf(QLatin1Char('_')) + 1; + QString group = layername.left(nameStartPosition) ; + + QString name = layername.right(layername.size() - nameStartPosition); + mTouchedLayers.append(name); + int indexOfLayer = mMapWork->indexOfLayer(name); + + bool found = false; + foreach (IndexByTileLayer *translationTable, mLayerList) { + const QString storedName = translationTable->keys().at(0)->name(); + // check if the group name is at the right position! index != -1 + // does not work, since the group name might be in the layer name + if (storedName.indexOf(group) == prefix.length()) { + translationTable->insert(tileLayer, indexOfLayer); + found = true; + break; + } + } + if (!found) { + mLayerList.append(new IndexByTileLayer()); + mLayerList.last()->insert(tileLayer, indexOfLayer); + } + } + + QString error; + + if (!mLayerRuleRegions) + error += tr("No ruleRegions layer found!") + QLatin1Char('\n'); + + if (mSetLayerIndex == -1) + error += tr("No set layers found!") + QLatin1Char('\n'); + + if (mLayerRuleSets.size() == 0) + error += tr("No ruleSet layer found!") + QLatin1Char('\n'); + + // no need to check for mLayerRuleNotSets.size() == 0 here. + // these layers are not necessary. + + if (!error.isEmpty()) { + error = mRulePath + QLatin1Char('\n') + error; + mError += error; + return false; + } + + return true; +} + +bool AutoMapper::setupRulesUsedCheck() +{ + TileLayer *setLayer = mMapWork->layerAt(mSetLayerIndex)->asTileLayer(); + QList tilesetWork = setLayer->usedTilesets().toList(); + foreach (const TileLayer *tilelayer, mLayerRuleSets) + foreach (const Tileset *tileset, tilelayer->usedTilesets()) + if (tileset->findSimilarTileset(tilesetWork)) + return true; + + foreach (const TileLayer *tilelayer, mLayerRuleNotSets) + foreach (const Tileset *tileset, tilelayer->usedTilesets()) + if (tileset->findSimilarTileset(tilesetWork)) + return true; + + return false; +} + +bool AutoMapper::setupRuleList() +{ + Q_ASSERT(mRules.isEmpty()); + Q_ASSERT(mLayerRuleRegions); + + for (int y = 1; y < mMapRules->height(); ++y ) { + for (int x = 1; x < mMapRules->width(); ++x ) { + if (!mLayerRuleRegions->cellAt(x, y).isEmpty() + && !isPartOfExistingRule(QPoint(x, y))) { + QRegion rule = createRule(x, y); + mRules << rule; + } + } + } + return true; +} + +bool AutoMapper::isPartOfExistingRule(const QPoint &p) const +{ + foreach (const QRegion ®ion, mRules) + if (region.contains(p)) + return true; + + return false; +} + +QRegion AutoMapper::createRule(int x, int y) const +{ + Q_ASSERT(mLayerRuleRegions); + QRegion ret(x, y, 1, 1); + QList addPoints; + const Cell &match = mLayerRuleRegions->cellAt(x, y); + addPoints.append(QPoint(x, y)); + + while (!addPoints.empty()) { + const QPoint current = addPoints.takeFirst(); + x = current.x(); + y = current.y(); + if (mLayerRuleRegions->contains(x - 1, y) + && mLayerRuleRegions->cellAt(x - 1, y) == match + && !ret.contains(QPoint(x - 1, y))) { + ret += QRegion(x - 1, y, 1, 1); + addPoints.append(QPoint(x - 1, y)); + } + if (mLayerRuleRegions->contains(x + 1, y) + && mLayerRuleRegions->cellAt(x + 1, y) == match + && !ret.contains(QPoint(x + 1, y))) { + ret += QRegion(x + 1, y, 1, 1); + addPoints.append(QPoint(x + 1, y)); + } + if (mLayerRuleRegions->contains(x, y - 1) + && mLayerRuleRegions->cellAt(x, y - 1) == match + && !ret.contains(QPoint(x, y - 1))) { + ret += QRegion(x, y - 1, 1, 1); + addPoints.append(QPoint(x, y - 1)); + } + if (mLayerRuleRegions->contains(x, y + 1) + && mLayerRuleRegions->cellAt(x, y + 1) == match + && !ret.contains(QPoint(x, y + 1))) { + ret += QRegion(x, y + 1, 1, 1); + addPoints.append(QPoint(x, y + 1)); + } + } + + return ret; +} + +bool AutoMapper::prepareAutoMap() +{ + mError.clear(); + mWarning.clear(); + + if (!setupMissingLayers()) + return false; + + if (!setupCorrectIndexes()) + return false; + + if (!setupTilesets(mMapRules, mMapWork)) + return false; + + return true; +} + +bool AutoMapper::setupMissingLayers() +{ + // make sure all needed layers are there: + foreach (const QString &name, mTouchedLayers) { + if (mMapWork->indexOfLayer(name) != -1) + continue; + + const int index = mMapWork->layerCount(); + TileLayer *tilelayer = new TileLayer(name, 0, 0, + mMapWork->width(), + mMapWork->height()); + mMapDocument->undoStack()->push( + new AddLayer(mMapDocument, index, tilelayer)); + mAddedTileLayers.append(name); + } + return true; +} + +bool AutoMapper::setupCorrectIndexes() +{ + // make sure all indexes of the layer translationtables are correct. + for (int i = 0; i < mLayerList.size(); ++i) { + IndexByTileLayer *translationTable = mLayerList.at(i); + foreach (TileLayer *tileLayerKey, translationTable->keys()) { + QString name = tileLayerKey->name(); + const int pos = name.indexOf(QLatin1Char('_')) + 1; + name = name.right(name.length() - pos); + + const int index = translationTable->value(tileLayerKey, -1); + if (index >= mMapWork->layerCount() || index == -1 || + name != mMapWork->layerAt(index)->name()) { + + int newIndex = mMapWork->indexOfLayer(name); + Q_ASSERT(newIndex != -1); + + translationTable->insert(tileLayerKey, newIndex); + } + } + } + // check the set layer as well: + if (mSetLayerIndex >= mMapWork->layerCount() || + mSetLayer != mMapWork->layerAt(mSetLayerIndex)->name()) { + + mSetLayerIndex = mMapWork->indexOfLayer(mSetLayer); + + if (mSetLayerIndex == -1) + return false; + } + return true; +} + +/** + * this cannot just be replaced by MapDocument::unifyTileset(Map), + * because here mAddedTileset is modified + */ +bool AutoMapper::setupTilesets(Map *src, Map *dst) +{ + QList existingTilesets = dst->tilesets(); + + // Add tilesets that are not yet part of dst map + foreach (Tileset *tileset, src->tilesets()) { + if (existingTilesets.contains(tileset)) + continue; + + QUndoStack *undoStack = mMapDocument->undoStack(); + + Tileset *replacement = tileset->findSimilarTileset(existingTilesets); + if (!replacement) { + mAddedTilesets.append(tileset); + undoStack->push(new AddTileset(mMapDocument, tileset)); + continue; + } + + // Merge the tile properties + const int sharedTileCount = qMin(tileset->tileCount(), + replacement->tileCount()); + for (int i = 0; i < sharedTileCount; ++i) { + Tile *replacementTile = replacement->tileAt(i); + Properties properties = replacementTile->properties(); + properties.merge(tileset->tileAt(i)->properties()); + + undoStack->push(new ChangeProperties(tr("Tile"), + replacementTile, + properties)); + } + src->replaceTileset(tileset, replacement); + + TilesetManager *tilesetManager = TilesetManager::instance(); + tilesetManager->addReference(replacement); + tilesetManager->removeReference(tileset); + } + return true; +} + +void AutoMapper::autoMap(QRegion *where) +{ + // first resize the active area + if (mAutoMappingRadius) { + QRegion region; + foreach (const QRect &r, where->rects()) { + region += r.adjusted(- mAutoMappingRadius, + - mAutoMappingRadius, + + mAutoMappingRadius, + + mAutoMappingRadius); + } + *where += region; + } + + // delete all the relevant area, if the property "DeleteTiles" is set + if (mDeleteTiles) + for (int i = 0; i < mLayerList.size(); ++i) { + IndexByTileLayer *translationTable = mLayerList.at(i); + foreach (TileLayer *tilelayer, translationTable->keys()) { + const int index = mLayerList.at(i)->value(tilelayer); + TileLayer *dstLayer = mMapWork->layerAt(index)->asTileLayer(); + clearRegion(dstLayer, *where); + } + } + + // Increase the given region where the next automapper should work. + // This needs to be done, so you can rely on the order of the rules at all + // locations + QRegion ret; + foreach (const QRect &rect, where->rects()) + foreach (const QRegion &rule, mRules) { + // at the moment the parallel execution does not work yet + // TODO: make multithreading available! + // either by dividing the rules or the region to multiple threads + ret = ret.united(applyRule(rule, rect)); + } + *where = where->united(ret); +} + +void AutoMapper::clearRegion(TileLayer *dstLayer, const QRegion &where) +{ + TileLayer *setLayer = mMapWork->layerAt(mSetLayerIndex)->asTileLayer(); + QRegion region = where.intersected(dstLayer->bounds()); + foreach (const QRect &rect, region.rects()) + for (int x = rect.left(); x <= rect.right(); ++x) + for (int y = rect.top(); y <= rect.bottom(); ++y) + if (setLayer->contains(x, y)) + if (!setLayer->cellAt(x, y).isEmpty()) + if (dstLayer->contains(x, y)) + dstLayer->setCell(x, y, Cell()); +} + +static bool compareLayerTo(const TileLayer *setLayer, + const QVector &listYes, + const QVector &listNo, + const QRegion &ruleRegion, const QPoint &offset); + +QRect AutoMapper::applyRule(const QRegion &rule, const QRect &where) +{ + QRect ret; + + if (mLayerList.isEmpty()) + return ret; + + QRect rbr = rule.boundingRect(); + + // Since the rule itself is translated, we need to adjust the borders of the + // loops. Decrease the size at all sides by one: There must be at least one + // tile overlap to the rule. + const int minX = where.left() - rbr.left() - rbr.width() + 1; + const int minY = where.top() - rbr.top() - rbr.height() + 1; + + const int maxX = where.right() - rbr.left() + rbr.width() - 1; + const int maxY = where.bottom() - rbr.top() + rbr.height() - 1; + TileLayer *setLayer = mMapWork->layerAt(mSetLayerIndex)->asTileLayer(); + + // In this list of regions it is stored which parts or the map have already + // been altered by exactly this rule. We store all the altered parts to + // make sure there are no overlaps of the same rule applied to + // (neighbouring) places + QList appliedRegions; + if (mNoOverlappingRules) + for (int i = 0; i < mMapWork->layerCount(); i++) + appliedRegions.append(QRegion()); + + for (int y = minY; y <= maxY; ++y) + for (int x = minX; x <= maxX; ++x) + if (compareLayerTo(setLayer, mLayerRuleSets, + mLayerRuleNotSets, rule, QPoint(x, y))) { + int r = 0; + // choose by chance which group of rule_layers should be used: + if (mLayerList.size() > 1) + r = qrand() % mLayerList.size(); + + if (!mNoOverlappingRules) { + copyMapRegion(rule, QPoint(x, y), mLayerList.at(r)); + ret = ret.united(rbr.translated(QPoint(x, y))); + continue; + } + + bool missmatch = false; + IndexByTileLayer *translationTable = mLayerList.at(r); + QList tileLayers = translationTable->keys(); + + // check if there are no overlaps within this rule. + QVector ruleRegionInLayer; + for (int i = 0; i < tileLayers.size(); ++i) { + TileLayer *tilelayer = tileLayers.at(i); + ruleRegionInLayer.append(tilelayer->region().intersected(rule)); + + if (appliedRegions.at(i).intersects( + ruleRegionInLayer[i].translated(x, y))) { + missmatch = true; + break; + } + } + if (!missmatch) { + copyMapRegion(rule, QPoint(x, y), mLayerList.at(r)); + ret = ret.united(rbr.translated(QPoint(x, y))); + for (int i = 0; i < translationTable->size(); ++i) { + appliedRegions[i] += + ruleRegionInLayer[i].translated(x, y); + } + } + } + + return ret; +} + +/** + * Returns a list of all cells which can be found within all tile layers + * within the given region. + */ +static QVector cellsInRegion(const QVector &list, + const QRegion &r) +{ + QVector cells; + foreach (const TileLayer *tilelayer, list) { + foreach (const QRect &rect, r.rects()) { + for (int x = rect.left(); x <= rect.right(); ++x) { + for (int y = rect.top(); y <= rect.bottom(); ++y) { + const Cell &cell = tilelayer->cellAt(x, y); + if (!cells.contains(cell)) + cells.append(cell); + } + } + } + } + return cells; +} + +/** + * This function is one of the core functions for understanding the + * automapping. + * In this function a certain region (of the set layer) is compared to + * several other layers (ruleSet and ruleNotSet). + * This comparision will determine if a rule of automapping matches, + * so if this rule is applied at this region given + * by a QRegion and Offset given by a QPoint. + * + * This compares the tile layer setLayer to several others given + * in the QList listYes (ruleSet) and OList listNo (ruleNotSet). + * The tile layer setLayer is examined at QRegion ruleRegion + offset + * The tile layers within listYes and listNo are examined at QRegion ruleRegion. + * + * Basically all matches between setLayer and a layer of listYes are considered + * good, while all matches between setLayer and listNo are considered bad and + * lead to canceling the comparison, returning false. + * + * The comparison is done for each position within the QRegion ruleRegion. + * If all positions of the region are considered "good" return true. + * + * Now there are several cases to distinguish: + * - setLayer is 0: + * obviously there should be no automapping. + * So here no rule should be applied. return false + * - setLayer is not 0: + * - both listYes and listNo are empty: + * should not happen, because with that configuration, absolutly + * no condition is given. + * return false, assuming this is an errornous rule being applied + * + * - both listYes and listNo are not empty: + * When comparing a tile at a certain position of tile layer setLayer + * to all available tiles in listYes, there must be at least + * one layer, in which there is a match of tiles of setLayer and +* listYes to consider this position good. + * In listNo there must not be a match to consider this position + * good. + * If there are no tiles within all available tiles within all layers + * of one list, all tiles in setLayer are considered good, + * while inspecting this list. + * All available tiles are all tiles within the whole rule region in + * all tile layers of the list. + * + * - either of both lists is empty + * When comparing a certain position of tile layer setLayer + * to all Tiles at the corresponding position this can happen: + * A tile of setLayer matches a tile of a layer in the list. Then this + * is considered as good, if the layer is from the listYes. + * Otherwise it is considered bad. + * + * Exception, when having only the listYes: + * if at the examined position there are no tiles within all Layers + * of the listYes, all tiles except all used tiles within + * the layers of that list are considered good. + * + * This exception was added to have a better functionality + * (need of less layers.) + * It was not added to the case, when having only listNo layers to + * avoid total symmetrie between those lists. + * + * If all positions are considered good, return true. + * return false otherwise. + * + * @return bool, if the tile layer matches the given list of layers. + */ +static bool compareLayerTo(const TileLayer *setLayer, + const QVector &listYes, + const QVector &listNo, + const QRegion &ruleRegion, const QPoint &offset) +{ + if (listYes.isEmpty() && listNo.isEmpty()) + return false; + + QVector cells; + if (listYes.isEmpty()) + cells = cellsInRegion(listNo, ruleRegion); + if (listNo.isEmpty()) + cells = cellsInRegion(listYes, ruleRegion); + + foreach (const QRect &rect, ruleRegion.rects()) { + for (int x = rect.left(); x <= rect.right(); ++x) { + for (int y = rect.top(); y <= rect.bottom(); ++y) { + // this is only used in the case where only one list has layers + // it is needed for the exception mentioned above + bool ruleDefinedListYes = false; + + bool matchListYes = false; + bool matchListNo = false; + + if (!setLayer->contains(x + offset.x(), y + offset.y())) + return false; + + const Cell &c1 = setLayer->cellAt(x + offset.x(), + y + offset.y()); + + // when there is no tile in setLayer, + // there should be no rule at all + if (c1.isEmpty()) + return false; + + // ruleDefined will be set when there is a tile in at least + // one layer. if there is a tile in at least one layer, only + // the given tiles in the different listYes layers are valid. + // if there is given no tile at all in the listYes layers, + // consider all tiles valid. + + foreach (const TileLayer *comparedTileLayer, listYes) { + + if (!comparedTileLayer->contains(x, y)) + return false; + + const Cell &c2 = comparedTileLayer->cellAt(x, y); + if (!c2.isEmpty()) + ruleDefinedListYes = true; + + if (!c2.isEmpty() && c1 == c2) + matchListYes = true; + } + foreach (const TileLayer *comparedTileLayer, listNo) { + + if (!comparedTileLayer->contains(x, y)) + return false; + + const Cell &c2 = comparedTileLayer->cellAt(x, y); + + if (!c2.isEmpty() && c1 == c2) + matchListNo = true; + } + + // when there are only layers in the listNo + // check only if these layers are unmatched + // no need to check explicitly the exception in this case. + if (listYes.isEmpty()) { + if (matchListNo) + return false; + else + continue; + } + // when there are only layers in the listYes + // check if these layers are matched, or if the exception works + if (listNo.isEmpty()) { + if (matchListYes) + continue; + if (!ruleDefinedListYes && !cells.contains(c1)) + continue; + return false; + } + + // there are layers in both lists: + // no need to consider ruleDefinedListXXX + if ((matchListYes || !ruleDefinedListYes) && !matchListNo) + continue; + else + return false; + } + } + } + return true; +} + +void AutoMapper::copyMapRegion(const QRegion ®ion, QPoint offset, + const IndexByTileLayer *layerTranslation) +{ + for (int i = 0; i < layerTranslation->keys().size(); ++i) { + TileLayer *from = layerTranslation->keys().at(i); + TileLayer *to = mMapWork->layerAt(layerTranslation->value(from))-> + asTileLayer(); + foreach (const QRect &rect, region.rects()) { + copyRegion(from, + rect.x(), rect.y(), + rect.width(), rect.height(), + to, + rect.x() + offset.x(), rect.y() + offset.y()); + } + } +} + +void AutoMapper::copyRegion(TileLayer *srcLayer, int srcX, int srcY, + int width, int height, + TileLayer *dstLayer, int dstX, int dstY) +{ + for (int x = 0; x < width; ++x) { + for (int y = 0; y < height; ++y) { + const Cell &cell = srcLayer->cellAt(srcX + x, srcY + y); + if (!cell.isEmpty()) { + // this is without graphics update, it's done afterwards for all + dstLayer->setCell(dstX + x, dstY + y, cell); + } + } + } +} + +void AutoMapper::cleanAll() +{ + cleanTilesets(); + cleanTileLayers(); +} + +void AutoMapper::cleanTilesets() +{ + foreach (Tileset *tileset, mAddedTilesets) { + if (mMapWork->isTilesetUsed(tileset)) + continue; + + const int layerIndex = mMapWork->indexOfTileset(tileset); + if (layerIndex == -1) + continue; + + QUndoStack *undo = mMapDocument->undoStack(); + undo->push(new RemoveTileset(mMapDocument, layerIndex, tileset)); + } + mAddedTilesets.clear(); +} + +void AutoMapper::cleanTileLayers() +{ + foreach (const QString &tilelayerName, mAddedTileLayers) { + const int layerIndex = mMapWork->indexOfLayer(tilelayerName); + if (layerIndex == -1) + continue; + + const TileLayer *tilelayer = mMapWork->layerAt(layerIndex)->asTileLayer(); + if (!tilelayer->isEmpty()) + continue; + + QUndoStack *undo = mMapDocument->undoStack(); + undo->push(new RemoveLayer(mMapDocument, layerIndex)); + } + mAddedTileLayers.clear(); +} + +void AutoMapper::cleanUpRulesMap() +{ + cleanTilesets(); + + // mMapRules can be empty, when in prepareLoad the very first stages fail. + if (!mMapRules) + return; + + TilesetManager *tilesetManager = TilesetManager::instance(); + tilesetManager->removeReferences(mMapRules->tilesets()); + + delete mMapRules; + mMapRules = 0; + + cleanUpRuleMapLayers(); + mRules.clear(); +} + +void AutoMapper::cleanUpRuleMapLayers() +{ + cleanTileLayers(); + + QList::const_iterator it; + for (it = mLayerList.constBegin(); it != mLayerList.constEnd(); ++it) + delete (*it); + + mLayerList.clear(); + // do not delete mLayerRuleRegions, it is owned by the map mapWork, which + // cares for it + mLayerRuleRegions = 0; + mLayerRuleSets.clear(); + mLayerRuleNotSets.clear(); +} + diff -Nru tiled-qt-0.7.1/src/tiled/automapper.h tiled-qt-0.8.0/src/tiled/automapper.h --- tiled-qt-0.7.1/src/tiled/automapper.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/automapper.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,365 @@ +/* + * automapper.h + * Copyright 2010-2011, Stefan Beller, stefanbeller@googlemail.com + * + * 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 . + */ + +#ifndef AUTOMAPPER_H +#define AUTOMAPPER_H + +#include +#include +#include + +#include +#include +#include + +namespace Tiled { + +class Map; +class TileLayer; +class Tileset; + +namespace Internal { + +class MapDocument; + +typedef QMap IndexByTileLayer; + +/** + * This class does all the work for the automapping feature. + * basically it can do the following: + * - check the rules map for rules and store them + * - compare TileLayers (i. e. check if/where a certain rule must be applied) + * - copy regions of Maps (multiple Layers, the layerlist is a + * lookup-table for matching the Layers) + */ +class AutoMapper : public QObject +{ + Q_OBJECT + +public: + /** + * Constructs a AutoMapper. + * + * @param workingDocument: the map to work on. + */ + AutoMapper(MapDocument *workingDocument, QString setlayer); + ~AutoMapper(); + + MapDocument *mapDocument() const { return mMapDocument; } + + QString ruleSetPath() const { return mRulePath; } + + /** + * This sets up some internal data structures, which do not change, + * so it is needed only when loading the rules map. + */ + bool prepareLoad(Map *rules, const QString &rulePath); + + /** + * Call prepareLoad first! Returns a set of strings describing the layers, + * which are likely touched. Actually this function returns all layers, + * which could be touched, when considering only the given layers of the + * rule map. + */ + QSet getTouchedLayers() const; + + /** + * This needs to be called directly before the autoMap call. + * It sets up some data structures which change rapidly, so it is quite + * painful to keep these datastructures up to date all time. (indices of + * layers of the working map) + */ + bool prepareAutoMap(); + + /** + * Here is done all the automapping. + */ + void autoMap(QRegion *where); + + /** + * This cleans all datastructures, which are setup via prepareAutoMap, + * so the auto mapper becomes ready for its next automatic mapping. + */ + void cleanAll(); + + /** + * Contains all errors until operation was canceled. + * The errorlist is cleared within prepareLoad and prepareAutoMap. + */ + QString errorString() const { return mError; } + + /** + * Contains all warnings which occur at loading a rules map or while + * automapping. + * The errorlist is cleared within prepareLoad and prepareAutoMap. + */ + QString warningString() const { return mWarning; } + +private: + /** + * Calls all setup-functions in the right order needed for processing + * a new rules file. + * + * param rulePath is only used to have better error message descriptions. + * + * @return returns true when anything is ok, false when errors occured. + * (in that case will be a msg box anyway) + */ + bool setupRulesMap(Map *rules, const QString &rulePath); + + void cleanUpRulesMap(); + + /** + * Sets up the set layer in the mapDocument, which is used for automapping + * @return returns true when anything is ok, false when errors occured. + * (in that case will be a msg box anyway) + */ + bool setupMapDocumentLayers(); + + /** + * Searches the rules layer for regions and stores these in \a rules. + * @return returns true when anything is ok, false when errors occured. + * (in that case will be a msg box anyway) + */ + bool setupRuleList(); + + /** + * Sets up the layers in the rules map, which are used for automapping. + * The layers are detected and put in the internal data structures + * @return returns true when anything is ok, false when errors occured. + * (in that case will be a msg box anyway) + */ + bool setupRuleMapTileLayers(); + + /** + * Checks if all needed layers in the working map are there. + * If not, add them in the correct order. + */ + bool setupMissingLayers(); + + /** + * Checks if the layers setup as in setupRuleMapLayers are still right. + * If it's not right, correct them. + * @return returns true if everything went fine. false is returned when + * no set layer was found + */ + bool setupCorrectIndexes(); + + /** + * sets up the tilesets which are used in automapping. + * @return returns true when anything is ok, false when errors occured. + * (in that case will be a msg box anyway) + */ + bool setupTilesets(Map *src, Map *dst); + + /** + * sets all tiles to 0 in the specified rectangle of the given tile layer. + */ + void clearRegion(TileLayer *dstLayer, const QRegion &where); + + /** + * This copies all Tiles from TileLayer src to TileLayer dst + * + * In src the Tiles are taken from the rectangle given by + * src_x, src_y, width and height. + * In dst they get copied to a rectangle given by + * dst_x, dst_y, width, height . + * if there is no tile in src TileLayer, there will nothing be copied, + * so the maybe existing tile in dst will not be overwritten. + * + */ + void copyRegion(TileLayer *src_lr, int src_x, int src_y, + int width, int height, TileLayer *dst_lr, + int dst_x, int dst_y); + + /** + * This copies multiple TileLayers from one map to another. + * To handle multiple Layers, LayerTranslation is a List of Pairs. + * The first of the QPair is the src and it gets copied to the Layer + * in second of QPair. + * Only the region \a region is considered for copying. + * In the destination it will come to the region translated by Offset. + */ + void copyMapRegion(const QRegion ®ion, QPoint Offset, + const IndexByTileLayer *LayerTranslation); + + /** + * This goes through all the positions of the mMapWork and checks if + * there fits the rule given by the region in mMapRuleSet. + * if there is a match all Layers are copied to mMapWork. + * @param rule: the region which should be compared to all positions + * of mMapWork + * @return where: an rectangle where the rule actually got applied + */ + QRect applyRule(const QRegion &rule, const QRect &where); + + /** + * This returns whether the given point \a p is part of an existing rule. + */ + bool isPartOfExistingRule(const QPoint &p) const; + + /** + * This creates a rule from a given point. + * So it will be checked, which regions are coherent to this point + * and all these regions will be treated as a new rule, + * which will be returned. To check what is coherent, a + * breadth first search will be performed, whereas each Tile is a node, + * and the 4 coherent tiles are connected to this node. + */ + QRegion createRule(int x, int y) const; + + /** + * Cleans up the data structes filled by setupRuleMapLayers(), + * so the next rule can be processed. + */ + void cleanUpRuleMapLayers(); + + /** + * Cleans up the data structes filled by setupTilesets(), + * so the next rule can be processed. + */ + void cleanTilesets(); + + /** + * Cleans up the added tile layers setup by setupMissingLayers(), + * so we have a minimal addition of tile layers by the automapping. + */ + void cleanTileLayers(); + + /** + * Checks if this the rules from the given rules map could be used anyway + * by comparing the used tilesets of the set layer and ruleset layer. + */ + bool setupRulesUsedCheck(); + + /** + * where to work in + */ + MapDocument *mMapDocument; + + /** + * the same as mMapDocument->map() + */ + Map *mMapWork; + + /** + * map containing the rules, usually different than mMapWork + */ + Map *mMapRules; + + /** + * This contains all added tilesets as pointers. + * if rules use Tilesets which are not in the mMapWork they are added. + * keep track of them, because we need to delete them afterwards, + * when they still are unused + * they will be added while setupTilesets(). + * they will be deleted at Destructor of AutoMapper. + */ + QVector mAddedTilesets; + + /** + * description see: mAddedTilesets, just described by Strings + */ + QList mAddedTileLayers; + + /** + * RuleRegions is the layer where the regions are defined. + */ + TileLayer *mLayerRuleRegions; + + /** + * mLayerSet is compared at each tile if it matches any Tile within the + * mLayerRuleSets list + * it must not match with any Tile to mLayerRuleNotSets + */ + QVector mLayerRuleSets; + QVector mLayerRuleNotSets; + + /** + * This stores the name of the layer, which is used in the working map to + * setup the automapper. + * Until this variable was introduced it was called "set" (hardcoded) + */ + QString mSetLayer; + + /** + * This is the index of the tile layer, which is used in the working map for + * automapping. + * So if anything is correct mMapWork->layerAt(mLayerSet)->name() + * equals mSetLayer. + */ + int mSetLayerIndex; + + /** + * List of Regions in mMapRules to know where the rules are + */ + QList mRules; + + /** + * The inner set with layers to indexes is needed for translating + * tile layers from mMapRules to mMapWork. + * + * The key is the pointer to the layer in the rulemap. The + * pointer to the layer within the working map is not hardwired, but the + * position in the layerlist, where it was found the last time. + * This loosely bound pointer ensures we will get the right layer, since we + * need to check before anyway, and it is still fast. + * + * The list is used to hold different translation tables + * => one of the tables is chosen by chance, so randomness is available + */ + QList mLayerList; + + /** + * store the name of the processed rules file, to have detailed + * error messages available + */ + QString mRulePath; + + /** + * determines if all tiles in all touched layers should be deleted first. + */ + bool mDeleteTiles; + + /** + * This variable determines, how many overlapping tiles should be used. + * The bigger the more area is remapped at an automapping operation. + * This can lead to higher latency, but provides a better behavior on + * interactive automapping. + * It defaults to zero. + */ + int mAutoMappingRadius; + + /** + * Determines if a rule is allowed to overlap itself. + */ + bool mNoOverlappingRules; + + QList mTouchedLayers; + + QString mError; + + QString mWarning; +}; + +} // namespace Internal +} // namespace Tiled + +#endif // AUTOMAPPER_H diff -Nru tiled-qt-0.7.1/src/tiled/automapperwrapper.cpp tiled-qt-0.8.0/src/tiled/automapperwrapper.cpp --- tiled-qt-0.7.1/src/tiled/automapperwrapper.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/automapperwrapper.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,126 @@ +/* + * automapperwrapper.cpp + * Copyright 2010-2011, Stefan Beller, stefanbeller@googlemail.com + * + * 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 "automapperwrapper.h" + +#include "map.h" +#include "mapdocument.h" +#include "tile.h" +#include "tilelayer.h" + +using namespace Tiled; +using namespace Tiled::Internal; + +AutoMapperWrapper::AutoMapperWrapper(MapDocument *mapDocument, QVector autoMapper, QRegion *where) +{ + mMapDocument = mapDocument; + Map *map = mMapDocument->map(); + + QSet touchedLayers; + foreach (AutoMapper *a, autoMapper) { + a->prepareAutoMap(); + touchedLayers|= a->getTouchedLayers(); + } + foreach (const QString &layerName, touchedLayers) { + const int layerindex = map->indexOfLayer(layerName); + Q_ASSERT(layerindex != -1); + mLayersBefore << static_cast(map->layerAt(layerindex)->clone()); + } + + foreach (AutoMapper *a, autoMapper) { + a->autoMap(where); + } + + foreach (const QString &layerName, touchedLayers) { + const int layerindex = map->indexOfLayer(layerName); + // layerindex exists, because AutoMapper is still alive, dont check + Q_ASSERT(layerindex != -1); + mLayersAfter << static_cast(map->layerAt(layerindex)->clone()); + } + // reduce memory usage by saving only diffs + Q_ASSERT(mLayersAfter.size() == mLayersBefore.size()); + for (int i = 0; i < mLayersAfter.size(); ++i) { + TileLayer *before = mLayersBefore.at(i); + TileLayer *after = mLayersAfter.at(i); + QRect diffRegion = before->computeDiffRegion(after).boundingRect(); + + TileLayer *before1 = before->copy(diffRegion); + TileLayer *after1 = after->copy(diffRegion); + + before1->setPosition(diffRegion.topLeft()); + after1->setPosition(diffRegion.topLeft()); + before1->setName(before->name()); + after1->setName(after->name()); + mLayersBefore.replace(i, before1); + mLayersAfter.replace(i, after1); + + delete before; + delete after; + } + + foreach (AutoMapper *a, autoMapper) { + a->cleanAll(); + } +} + +AutoMapperWrapper::~AutoMapperWrapper() +{ + QVector::iterator i; + for (i = mLayersAfter.begin(); i != mLayersAfter.end(); ++i) + delete *i; + for (i = mLayersBefore.begin(); i != mLayersBefore.end(); ++i) + delete *i; +} + +void AutoMapperWrapper::undo() +{ + Map *map = mMapDocument->map(); + QVector::iterator i; + for (i = mLayersBefore.begin(); i != mLayersBefore.end(); ++i) { + const int layerindex = map->indexOfLayer((*i)->name()); + if (layerindex != -1) + patchLayer(layerindex, *i); + } +} + +void AutoMapperWrapper::redo() +{ + Map *map = mMapDocument->map(); + QVector::iterator i; + for (i = mLayersAfter.begin(); i != mLayersAfter.end(); ++i) { + const int layerindex = (map->indexOfLayer((*i)->name())); + if (layerindex != -1) + patchLayer(layerindex, *i); + } + +} + +void AutoMapperWrapper::patchLayer(int layerIndex, TileLayer *layer) +{ + Map *map = mMapDocument->map(); + QRect b = layer->bounds(); + + Q_ASSERT(map->layerAt(layerIndex)->asTileLayer()); + TileLayer *t = static_cast(map->layerAt(layerIndex)); + + t->setCells(b.left() - t->x(), b.top() - t->y(), layer, + b.translated(-t->position())); + mMapDocument->emitRegionChanged(b); +} diff -Nru tiled-qt-0.7.1/src/tiled/automapperwrapper.h tiled-qt-0.8.0/src/tiled/automapperwrapper.h --- tiled-qt-0.7.1/src/tiled/automapperwrapper.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/automapperwrapper.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,63 @@ +/* + * automapperwrapper.h + * Copyright 2010-2011, Stefan Beller, stefanbeller@googlemail.com + * + * 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 . + */ + +#ifndef AUTOMAPPERWRAPPER_H +#define AUTOMAPPERWRAPPER_H + +#include "automapper.h" + +#include +#include + +namespace Tiled { + +namespace Internal { + +class MapDocument; + +/** + * This is a wrapper class for the AutoMapper class. + * Here in this class only undo/redo functionality all rulemaps + * is provided. + * This class will take a snapshot of the layers before and after the + * automapping is done. In between instances of AutoMapper are doing the work. + */ +class AutoMapperWrapper : public QUndoCommand +{ +public: + AutoMapperWrapper(MapDocument *mapDocument, QVector autoMapper, + QRegion *where); + ~AutoMapperWrapper(); + + void undo(); + void redo(); + +private: + void patchLayer(int layerIndex, TileLayer *layer); + + MapDocument *mMapDocument; + QVector mLayersAfter; + QVector mLayersBefore; +}; + +} // namespace Internal +} // namespace Tiled + +#endif // AUTOMAPPERWRAPPER_H diff -Nru tiled-qt-0.7.1/src/tiled/automappingmanager.cpp tiled-qt-0.8.0/src/tiled/automappingmanager.cpp --- tiled-qt-0.7.1/src/tiled/automappingmanager.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/automappingmanager.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,280 @@ +/* + * automappingmanager.cpp + * Copyright 2010-2011, Stefan Beller, stefanbeller@googlemail.com + * + * 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 "automappingmanager.h" + +#include "automapperwrapper.h" +#include "map.h" +#include "mapdocument.h" +#include "tilelayer.h" +#include "tilesetmanager.h" +#include "tmxmapreader.h" + +#include +#include +#include + +using namespace Tiled; +using namespace Tiled::Internal; + +AutomappingManager *AutomappingManager::mInstance = 0; + +AutomappingManager::AutomappingManager(QObject *parent) + : QObject(parent) + , mMapDocument(0) + , mLoaded(false) + , mWatcher(new QFileSystemWatcher(this)) +{ + connect(mWatcher, SIGNAL(fileChanged(QString)), + this, SLOT(fileChanged(QString))); + mChangedFilesTimer.setInterval(100); + mChangedFilesTimer.setSingleShot(true); + connect(&mChangedFilesTimer, SIGNAL(timeout()), + this, SLOT(fileChangedTimeout())); + // this should be stored in the project file later on. + // now just default to the value we always had. + mSetLayer = QLatin1String("set"); +} + +AutomappingManager::~AutomappingManager() +{ + cleanUp(); + delete mWatcher; +} + +AutomappingManager *AutomappingManager::instance() +{ + if (!mInstance) + mInstance = new AutomappingManager(0); + + return mInstance; +} + +void AutomappingManager::deleteInstance() +{ + delete mInstance; + mInstance = 0; +} + +void AutomappingManager::autoMap() +{ + if (!mMapDocument) + return; + + Map *map = mMapDocument->map(); + int w = map->width(); + int h = map->height(); + int l = map->indexOfLayer(mSetLayer); + + Layer *passedLayer = 0; + if (l != -1) + passedLayer = map->layerAt(l); + + autoMap(QRect(0, 0, w, h), passedLayer); +} + +void AutomappingManager::autoMap(QRegion where, Layer *l) +{ + mError.clear(); + mWarning.clear(); + if (!mMapDocument) { + mError = tr("No map document found!") + QLatin1Char('\n'); + emit errorsOccurred(); + return; + } + + if (!l) { + mError = tr("No set layer found!") + QLatin1Char('\n'); + emit errorsOccurred(); + return; + } + + if (l->name() != mSetLayer) + return; + + if (!mLoaded) { + const QString mapPath = QFileInfo(mMapDocument->fileName()).path(); + const QString rulesFileName = mapPath + QLatin1String("/rules.txt"); + if (loadFile(rulesFileName)) { + mLoaded = true; + } else { + emit errorsOccurred(); + return; + } + } + + Map *map = mMapDocument->map(); + QString layer = map->layerAt(mMapDocument->currentLayerIndex())->name(); + + // use a pointer to the region, so each automapper can manipulate it and the + // following automappers do see the impact + QRegion *passedRegion = new QRegion(where); + + QUndoStack *undoStack = mMapDocument->undoStack(); + undoStack->beginMacro(tr("Apply AutoMap rules")); + AutoMapperWrapper *aw = new AutoMapperWrapper(mMapDocument, mAutoMappers, passedRegion); + undoStack->push(aw); + undoStack->endMacro(); + + foreach (AutoMapper *automapper, mAutoMappers) { + mWarning += automapper->warningString(); + mError += automapper->errorString(); + } + + mMapDocument->emitRegionChanged(*passedRegion); + delete passedRegion; + mMapDocument->setCurrentLayerIndex(map->indexOfLayer(layer)); + + if (!mWarning.isEmpty()) + emit warningsOccurred(); + + if (!mError.isEmpty()) + emit errorsOccurred(); +} + +bool AutomappingManager::loadFile(const QString &filePath) +{ + bool ret = true; + const QString absPath = QFileInfo(filePath).path(); + QFile rulesFile(filePath); + + if (!rulesFile.exists()) { + mError += tr("No rules file found at:\n%1").arg(filePath) + + QLatin1Char('\n'); + return false; + } + if (!rulesFile.open(QIODevice::ReadOnly)) { + mError += tr("Error opening rules file:\n%1").arg(filePath) + + QLatin1Char('\n'); + return false; + } + + QTextStream in(&rulesFile); + QString line = in.readLine(); + + for (; !line.isNull(); line = in.readLine()) { + QString rulePath = line.trimmed(); + if (rulePath.isEmpty() + || rulePath.startsWith(QLatin1Char('#')) + || rulePath.startsWith(QLatin1String("//"))) + continue; + + if (QFileInfo(rulePath).isRelative()) + rulePath = absPath + QLatin1Char('/') + rulePath; + + if (!QFileInfo(rulePath).exists()) { + mError += tr("File not found:\n%1").arg(rulePath) + QLatin1Char('\n'); + ret = false; + continue; + } + if (rulePath.endsWith(QLatin1String(".tmx"), Qt::CaseInsensitive)){ + TmxMapReader mapReader; + + Map *rules = mapReader.read(rulePath); + + TilesetManager *tilesetManager = TilesetManager::instance(); + tilesetManager->addReferences(rules->tilesets()); + + if (!rules) { + mError += tr("Opening rules map failed:\n%1").arg( + mapReader.errorString()) + QLatin1Char('\n'); + ret = false; + continue; + } + AutoMapper *autoMapper; + autoMapper = new AutoMapper(mMapDocument, mSetLayer); + + bool loadSuccess = autoMapper->prepareLoad(rules, rulePath); + mWarning += autoMapper->warningString(); + if (loadSuccess) { + mAutoMappers.append(autoMapper); + } else { + mError += autoMapper->errorString(); + delete autoMapper; + } + } + if (rulePath.endsWith(QLatin1String(".txt"), Qt::CaseInsensitive)){ + if (!loadFile(rulePath)) + ret = false; + } + } + return ret; +} + +void AutomappingManager::setMapDocument(MapDocument *mapDocument) +{ + cleanUp(); + if (mMapDocument) + mMapDocument->disconnect(this); + + mMapDocument = mapDocument; + + if (mMapDocument) + connect(mMapDocument, SIGNAL(regionEdited(QRegion,Layer*)), + this, SLOT(autoMap(QRegion,Layer*))); + mLoaded = false; +} + +void AutomappingManager::cleanUp() +{ + foreach (const AutoMapper *autoMapper, mAutoMappers) { + delete autoMapper; + } + mAutoMappers.clear(); +} + +void AutomappingManager::fileChanged(const QString &path) +{ + /* + * Use a one-shot timer and wait a little, to be sure there are no + * further modifications within very short time. + */ + if (!mChangedFiles.contains(path)) { + mChangedFiles.insert(path); + mChangedFilesTimer.start(); + } +} + +void AutomappingManager::fileChangedTimeout() +{ + for (int i = 0; i != mAutoMappers.size(); ++i) { + AutoMapper *am = mAutoMappers.at(i); + QString fileName = am->ruleSetPath(); + if (mChangedFiles.contains(fileName)) { + delete am; + + TmxMapReader mapReader; + Map *rules = mapReader.read(fileName); + if (!rules) { + mAutoMappers.remove(i); + continue; + } + + AutoMapper *autoMapper = new AutoMapper(mMapDocument, mSetLayer); + if (autoMapper->prepareLoad(rules, fileName)) { + mAutoMappers.replace(i, autoMapper); + } else { + delete autoMapper; + mAutoMappers.remove(i); + } + } + } + mChangedFiles.clear(); +} diff -Nru tiled-qt-0.7.1/src/tiled/automappingmanager.h tiled-qt-0.8.0/src/tiled/automappingmanager.h --- tiled-qt-0.7.1/src/tiled/automappingmanager.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/automappingmanager.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,196 @@ +/* + * automappingmanager.h + * Copyright 2010-2011, Stefan Beller, stefanbeller@googlemail.com + * + * 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 . + */ + +#ifndef AUTOMAPPINGMANAGER_H +#define AUTOMAPPINGMANAGER_H + +#include +#include +#include +#include +#include + +class QFileSystemWatcher; +class QObject; + +namespace Tiled { + +class Layer; +class Map; +class TileLayer; +class Tileset; + +namespace Internal { + +class AutoMapper; +class MapDocument; + +/** + * This class is a superior class to the AutoMapper and AutoMapperWrapper class. + * It uses these classes to do the whole automapping process. + */ +class AutomappingManager: public QObject +{ + Q_OBJECT + +public: + /** + * Requests the AutomaticMapping manager. When the manager doesn't exist + * yet, it will be created. + */ + static AutomappingManager *instance(); + + /** + * Deletes the AutomaticMapping manager instance, when it exists. + */ + static void deleteInstance(); + + /** + * This triggers an automapping on the whole current map document. + */ + void autoMap(); + + void setMapDocument(MapDocument *mapDocument); + + QString errorString() const { return mError; } + + QString warningString() const { return mWarning; } + +signals: + /** + * This signal is emited after automapping was done and an error occurred. + */ + void errorsOccurred(); + + /** + * This signal is emited after automapping was done and a warning occurred. + */ + void warningsOccurred(); + +public slots: + /** + * This sets up new AutoMapperWrappers, which trigger the automapping. + * The region 'where' describes where only the automapping takes place. + * This is a signal so it can directly be connected to the regionEdited + * signal of map documents. + */ + void autoMap(QRegion where, Layer *layer); + +private slots: + /** + * connected to the QFileWatcher, which monitors all rules files for changes + */ + void fileChanged(const QString &path); + + /** + * This is connected to the timer, which fires once after the files changed. + */ + void fileChangedTimeout(); + +private: + Q_DISABLE_COPY(AutomappingManager) + + /** + * Constructor. Only used by the AutomaticMapping manager itself. + */ + AutomappingManager(QObject *parent); + + ~AutomappingManager(); + + static AutomappingManager *mInstance; + + /** + * This function parses a rules file. + * For each path which is a rule, (fileextension is tmx) an AutoMapper + * object is setup. + * + * If a fileextension is txt, this file will be opened and searched for + * rules again. + * + * @return if the loading was successful: return true if it suceeded. + */ + bool loadFile(const QString &filePath); + + /** + * deletes all its data structures + */ + void cleanUp(); + + /** + * The current map document. + */ + MapDocument *mMapDocument; + + /** + * For each new file of rules a new AutoMapper is setup. In this vector we + * can store all of the AutoMappers in order. + */ + QVector mAutoMappers; + + /** + * This tells you if the rules for the current map document were already + * loaded. + */ + bool mLoaded; + + /** + * All the used rulefiles are monitored by this object, so in case of + * external changes it will automatically reloaded. + */ + QFileSystemWatcher *mWatcher; + + /** + * All external changed files will be put in here, so these will be loaded + * altogether when the timer expires. + */ + QSet mChangedFiles; + + /** + * This timer is started when the first file was modified. The files are + * actually reloaded after this timer expires, so just in case the files + * get modified within short time delays (some editors do so), it will + * wait until all is over an reload everything at the timeout. + */ + QTimer mChangedFilesTimer; + + /** + * Contains all errors which occurred until canceling. + * If mError is not empty, no serious result can be expected. + */ + QString mError; + + /** + * Contains all strings, which try to explain unusual and unexpected + * behavior. + */ + QString mWarning; + + /** + * This stores the name of the layer, which is used in the working map to + * setup the automapper. + * Until this variable was introduced it was called "set" (hardcoded) + */ + QString mSetLayer; +}; + +} // namespace Internal +} // namespace Tiled + +#endif // AUTOMAPPINGMANAGER_H diff -Nru tiled-qt-0.7.1/src/tiled/brushitem.cpp tiled-qt-0.8.0/src/tiled/brushitem.cpp --- tiled-qt-0.7.1/src/tiled/brushitem.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/brushitem.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -136,11 +136,14 @@ // Adjust for amount of pixels tiles extend at the top and to the right if (mTileLayer) { const Map *map = mMapDocument->map(); - const int tileWidth = map->tileWidth(); - const int tileHeight = map->tileHeight(); - const QSize maxTileSize = mTileLayer->maxTileSize(); - const int extendTop = -qMax(0, maxTileSize.height() - tileHeight); - const int extendRight = qMax(0, maxTileSize.width() - tileWidth); - mBoundingRect.adjust(0, extendTop, extendRight, 0); + + QMargins drawMargins = mTileLayer->drawMargins(); + drawMargins.setTop(drawMargins.top() - map->tileHeight()); + drawMargins.setRight(drawMargins.right() - map->tileWidth()); + + mBoundingRect.adjust(-drawMargins.left(), + -drawMargins.top(), + drawMargins.right(), + drawMargins.bottom()); } } diff -Nru tiled-qt-0.7.1/src/tiled/changeobjectgroupproperties.cpp tiled-qt-0.8.0/src/tiled/changeobjectgroupproperties.cpp --- tiled-qt-0.7.1/src/tiled/changeobjectgroupproperties.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/changeobjectgroupproperties.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -46,11 +46,11 @@ void ChangeObjectGroupProperties::redo() { mObjectGroup->setColor(mRedoColor); - mMapDocument->emitRegionChanged(mObjectGroup->bounds()); + mMapDocument->emitObjectsChanged(mObjectGroup->objects()); } void ChangeObjectGroupProperties::undo() { mObjectGroup->setColor(mUndoColor); - mMapDocument->emitRegionChanged(mObjectGroup->bounds()); + mMapDocument->emitObjectsChanged(mObjectGroup->objects()); } diff -Nru tiled-qt-0.7.1/src/tiled/changepolygon.cpp tiled-qt-0.8.0/src/tiled/changepolygon.cpp --- tiled-qt-0.7.1/src/tiled/changepolygon.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/changepolygon.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,52 @@ +/* + * changepolygon.cpp + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 "changepolygon.h" + +#include "mapdocument.h" +#include "mapobject.h" + +#include + +using namespace Tiled; +using namespace Tiled::Internal; + +ChangePolygon::ChangePolygon(MapDocument *mapDocument, + MapObject *mapObject, + const QPolygonF &oldPolygon) + : mMapDocument(mapDocument) + , mMapObject(mapObject) + , mOldPolygon(oldPolygon) + , mNewPolygon(mapObject->polygon()) +{ + setText(QCoreApplication::translate("Undo Commands", "Change Polygon")); +} + +void ChangePolygon::undo() +{ + mMapObject->setPolygon(mOldPolygon); + mMapDocument->emitObjectChanged(mMapObject); +} + +void ChangePolygon::redo() +{ + mMapObject->setPolygon(mNewPolygon); + mMapDocument->emitObjectChanged(mMapObject); +} diff -Nru tiled-qt-0.7.1/src/tiled/changepolygon.h tiled-qt-0.8.0/src/tiled/changepolygon.h --- tiled-qt-0.7.1/src/tiled/changepolygon.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/changepolygon.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,62 @@ +/* + * changepolygon.h + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 . + */ + +#ifndef CHANGEPOLYGON_H +#define CHANGEPOLYGON_H + +#include +#include + +namespace Tiled { + +class MapObject; + +namespace Internal { + +class MapDocument; + +/** + * Changes the polygon of a MapObject. + * + * This class expects the polygon to be already changed, and takes the previous + * polygon in the constructor. + */ +class ChangePolygon : public QUndoCommand +{ +public: + ChangePolygon(MapDocument *mapDocument, + MapObject *mapObject, + const QPolygonF &oldPolygon); + + void undo(); + void redo(); + +private: + MapDocument *mMapDocument; + MapObject *mMapObject; + + QPolygonF mOldPolygon; + QPolygonF mNewPolygon; +}; + +} // namespace Internal +} // namespace Tiled + +#endif // CHANGEPOLYGON_H diff -Nru tiled-qt-0.7.1/src/tiled/clipboardmanager.cpp tiled-qt-0.8.0/src/tiled/clipboardmanager.cpp --- tiled-qt-0.7.1/src/tiled/clipboardmanager.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/clipboardmanager.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -88,7 +88,7 @@ -tileLayer->y())); } else if (!selectedObjects.isEmpty()) { // Create a new object group with clones of the selected objects - ObjectGroup *objectGroup = new ObjectGroup(QString(), 0, 0, 0, 0); + ObjectGroup *objectGroup = new ObjectGroup; foreach (const MapObject *mapObject, selectedObjects) objectGroup->addObject(mapObject->clone()); copyLayer = objectGroup; diff -Nru tiled-qt-0.7.1/src/tiled/commandbutton.cpp tiled-qt-0.8.0/src/tiled/commandbutton.cpp --- tiled-qt-0.7.1/src/tiled/commandbutton.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/commandbutton.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -23,6 +23,7 @@ #include "commanddialog.h" #include "utils.h" +#include #include #include @@ -36,8 +37,7 @@ { setIcon(QIcon(QLatin1String(":images/24x24/system-run.png"))); setThemeIcon(this, "system-run"); - setToolTip(tr("Execute Command")); - setShortcut(QKeySequence(tr("F5"))); + retranslateUi(); setPopupMode(QToolButton::MenuButtonPopup); setMenu(mMenu); @@ -113,3 +113,22 @@ connect(action, SIGNAL(triggered()), SLOT(showDialog())); mMenu->addAction(action); } + +void CommandButton::changeEvent(QEvent *event) +{ + QToolButton::changeEvent(event); + + switch (event->type()) { + case QEvent::LanguageChange: + retranslateUi(); + break; + default: + break; + } +} + +void CommandButton::retranslateUi() +{ + setToolTip(tr("Execute Command")); + setShortcut(QKeySequence(tr("F5"))); +} diff -Nru tiled-qt-0.7.1/src/tiled/commandbutton.h tiled-qt-0.8.0/src/tiled/commandbutton.h --- tiled-qt-0.7.1/src/tiled/commandbutton.h 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/commandbutton.h 2011-12-11 21:49:29.000000000 +0000 @@ -38,12 +38,17 @@ public: CommandButton(QWidget *parent); +protected: + void changeEvent(QEvent *event); + private slots: void runCommand(); void showDialog(); void populateMenu(); private: + void retranslateUi(); + QMenu *mMenu; }; diff -Nru tiled-qt-0.7.1/src/tiled/command.cpp tiled-qt-0.8.0/src/tiled/command.cpp --- tiled-qt-0.7.1/src/tiled/command.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/command.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -24,7 +24,6 @@ #include #include -#include using namespace Tiled; using namespace Tiled::Internal; @@ -91,6 +90,9 @@ : QProcess(DocumentManager::instance()) , mName(command.name) , mFinalCommand(command.finalCommand()) +#ifdef Q_WS_MAC + , mFile(QLatin1String("tiledXXXXXX.command")) +#endif { // Give an error if the command is empty or just whitespace if (mFinalCommand.trimmed().isEmpty()) { @@ -116,21 +118,20 @@ // application to see the output. // Create and write the command to a .command file - QTemporaryFile tmp(QLatin1String("tiledXXXXXX.command")); - tmp.setAutoRemove(false); - if (!tmp.open()) { - handleError(tr("Unable to create/open %1").arg(tmp.fileName())); + + if (!mFile.open()) { + handleError(tr("Unable to create/open %1").arg(mFile.fileName())); return; } - tmp.write(mFinalCommand.toStdString().c_str()); - tmp.close(); + mFile.write(mFinalCommand.toStdString().c_str()); + mFile.close(); // Add execute permission to the file int chmodRet = QProcess::execute(QString(QLatin1String( - "chmod +x \"%1\"")).arg(tmp.fileName())); + "chmod +x \"%1\"")).arg(mFile.fileName())); if (chmodRet != 0) { handleError(tr("Unable to add executable permissions to %1") - .arg(tmp.fileName())); + .arg(mFile.fileName())); return; } @@ -138,16 +139,16 @@ // -W makes it not return immediately // -n makes it open a new instance of terminal if it is open already mFinalCommand = QString(QLatin1String("open -W -n \"%1\"")) - .arg(tmp.fileName()); + .arg(mFile.fileName()); #endif } - start(mFinalCommand); - connect(this, SIGNAL(error(QProcess::ProcessError)), SLOT(handleError(QProcess::ProcessError))); connect(this, SIGNAL(finished(int)), SLOT(deleteLater())); + + start(mFinalCommand); } void CommandProcess::handleError(QProcess::ProcessError error) diff -Nru tiled-qt-0.7.1/src/tiled/commanddialog.cpp tiled-qt-0.8.0/src/tiled/commanddialog.cpp --- tiled-qt-0.7.1/src/tiled/commanddialog.cpp 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/commanddialog.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -35,6 +35,8 @@ , mUi(new Ui::CommandDialog) { mUi->setupUi(this); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + mUi->saveBox->setChecked(mUi->treeView->model()->saveBeforeExecute()); setWindowTitle(tr("Edit Commands")); diff -Nru tiled-qt-0.7.1/src/tiled/command.h tiled-qt-0.8.0/src/tiled/command.h --- tiled-qt-0.7.1/src/tiled/command.h 2011-09-27 18:24:59.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/command.h 2011-12-11 21:49:29.000000000 +0000 @@ -25,6 +25,10 @@ #include #include +#ifdef Q_WS_MAC +#include +#endif + namespace Tiled { namespace Internal { @@ -78,6 +82,10 @@ QString mName; QString mFinalCommand; + +#ifdef Q_WS_MAC + QTemporaryFile mFile; +#endif }; } // namespace Internal diff -Nru tiled-qt-0.7.1/src/tiled/commandlineparser.cpp tiled-qt-0.8.0/src/tiled/commandlineparser.cpp --- tiled-qt-0.7.1/src/tiled/commandlineparser.cpp 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/commandlineparser.cpp 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,175 @@ +/* + * commandlineparser.cpp + * Copyright 2011, Ben Longbons + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 "commandlineparser.h" + +#include +#include + +using namespace Tiled; +using namespace Tiled::Internal; + +CommandLineParser::CommandLineParser() + : mLongestArgument(0) + , mShowHelp(false) +{ +} + +void CommandLineParser::registerOption(Callback callback, + void *data, + QChar shortName, + const QString &longName, + const QString &help) +{ + mOptions.append(Option(callback, data, shortName, longName, help)); + + const int length = longName.length(); + if (mLongestArgument < length) + mLongestArgument = length; +} + +bool CommandLineParser::parse(const QStringList &arguments) +{ + mFilesToOpen.clear(); + mShowHelp = false; + + QStringList todo = arguments; + mCurrentProgramName = QFileInfo(todo.takeFirst()).fileName(); + + int index = 0; + bool noMoreArguments = false; + + while (!todo.isEmpty()) { + index++; + const QString arg = todo.takeFirst(); + + if (arg.isEmpty()) + continue; + + if (noMoreArguments || arg.at(0) != QLatin1Char('-')) { + mFilesToOpen.append(arg); + continue; + } + + if (arg.length() == 1) { + // Traditionally a single hyphen means read file from stdin, + // write file to stdout. This isn't supported right now. + qWarning().nospace() << "Bad argument " << index + << ": lonely hyphen"; + showHelp(); + return false; + } + + // Long options + if (arg.at(1) == QLatin1Char('-')) { + // Double hypen "--" means no more options will follow + if (arg.length() == 2) { + noMoreArguments = true; + continue; + } + + if (!handleLongOption(arg)) { + qWarning().nospace() << "Unknown long argument " << index + << ": " << arg; + mShowHelp = true; + break; + } + + continue; + } + + // Short options + for (int i = 1; i < arg.length(); ++i) { + const QChar c = arg.at(i); + if (!handleShortOption(c)) { + qWarning().nospace() << "Unknown short argument " << index + << '.' << i << ": " << c; + mShowHelp = true; + break; + } + } + } + + if (mShowHelp) { + showHelp(); + return false; + } + + return true; +} + +void CommandLineParser::showHelp() +{ + // TODO: Make translatable + qWarning().nospace() << "Usage: " << qPrintable(mCurrentProgramName) + << " [options] [files...]\n\n" + << "Options:"; + + qWarning(" -h %-*s : Display this help", mLongestArgument, "--help"); + + foreach (const Option &option, mOptions) { + if (!option.shortName.isNull()) { + qWarning(" -%c %-*s : %s", + option.shortName.toLatin1(), + mLongestArgument, qPrintable(option.longName), + qPrintable(option.help)); + } else { + qWarning(" %-*s : %s", + mLongestArgument, qPrintable(option.longName), + qPrintable(option.help)); + + } + } +} + +bool CommandLineParser::handleLongOption(const QString &longName) +{ + if (longName == QLatin1String("--help")) { + mShowHelp = true; + return true; + } + + foreach (const Option &option, mOptions) { + if (longName == option.longName) { + option.callback(option.data); + return true; + } + } + + return false; +} + +bool CommandLineParser::handleShortOption(QChar c) +{ + if (c == QLatin1Char('h')) { + mShowHelp = true; + return true; + } + + foreach (const Option &option, mOptions) { + if (c == option.shortName) { + option.callback(option.data); + return true; + } + } + + return false; +} diff -Nru tiled-qt-0.7.1/src/tiled/commandlineparser.h tiled-qt-0.8.0/src/tiled/commandlineparser.h --- tiled-qt-0.7.1/src/tiled/commandlineparser.h 1970-01-01 00:00:00.000000000 +0000 +++ tiled-qt-0.8.0/src/tiled/commandlineparser.h 2011-12-11 21:49:29.000000000 +0000 @@ -0,0 +1,147 @@ +/* + * commandlineparser.h + * Copyright 2011, Ben Longbons + * Copyright 2011, Thorbjørn Lindeijer + * + * 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 . + */ + +#ifndef COMMANDLINEPARSER_H +#define COMMANDLINEPARSER_H + +#include +#include + +namespace Tiled { +namespace Internal { + +/** + * C-style callback function taking an arbitrary data pointer. + */ +typedef void (*Callback)(void *data); + +/** + * A template function that will static-cast the given \a object to a type T + * and call the member function of T given in the second template argument. + */ +template +void MemberFunctionCall(void *object) +{ + T *t = static_cast(object); + (t->*memberFunction)(); +} + + +/** + * A simple command line parser. Options should be registered through + * registerOption(). + * + * The help option (-h/--help) is provided by the parser based on the + * registered options. + */ +class CommandLineParser +{ + +public: + CommandLineParser(); + + /** + * Registers an option with the parser. When an option with the given + * \a shortName or \a longName is encountered, \a callback is called with + * \a data as its only parameter. + */ + void registerOption(Callback callback, + void *data, + QChar shortName, + const QString &longName, + const QString &help); + + /** + * Convenience overload that allows registering an option with a callback + * as a member function of a class. The class type and the member function + * are given as template parameters, while the instance is passed in as + * \a handler. + * + * \overload + */ + template + void registerOption(T *handler, + QChar shortName, + const QString &longName, + const QString &help) + { + registerOption(&MemberFunctionCall, + handler, + shortName, + longName, + help); + } + + /** + * Parses the given \a arguments. Returns false when the application is not + * expected to run (either there was a parsing error, or the help was + * requested). + */ + bool parse(const QStringList &arguments); + + /** + * Returns the files to open that were found among the arguments. + */ + const QStringList &filesToOpen() const { return mFilesToOpen; } + +private: + void showHelp(); + + bool handleLongOption(const QString &longName); + bool handleShortOption(QChar c); + + /** + * Internal definition of a command line option. + */ + struct Option + { + Option() {} + + Option(Callback callback, + void *data, + QChar shortName, + const QString &longName, + const QString &help) + : callback(callback) + , data(data) + , shortName(shortName) + , longName(longName) + , help(help) + {} + + Callback callback; + void *data; + QChar shortName; + QString longName; + QString help; + }; + + QVector