1 /*
2  * exporthelper.cpp
3  * Copyright 2018, Thorbjørn Lindeijer <bjorn@lindeijer.nl>
4  *
5  * This file is part of Tiled.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "exporthelper.h"
22 
23 #include "mapobject.h"
24 #include "objectgroup.h"
25 
26 namespace Tiled {
27 
28 /**
29  * @return the format options that should be used when writing the file.
30  */
formatOptions() const31 FileFormat::Options ExportHelper::formatOptions() const
32 {
33     FileFormat::Options options;
34     if (mOptions.testFlag(Preferences::ExportMinimized))
35         options |= FileFormat::WriteMinimized;
36     return options;
37 }
38 
39 /**
40  * Prepares a tileset for export.
41  *
42  * \a savingTileset means that this tileset is being saved to its own file
43  * rather saved as part of a map. In this case, we want to apply some export
44  * options that we would skip for external tilesets.
45  */
prepareExportTileset(const SharedTileset & tileset,bool savingTileset) const46 SharedTileset ExportHelper::prepareExportTileset(const SharedTileset &tileset,
47                                                  bool savingTileset) const
48 {
49     const bool hasExportSettings = !(tileset->exportFileName.isEmpty()
50                                      && tileset->exportFormat.isEmpty());
51 
52     if (!mOptions && !hasExportSettings)
53         return tileset;
54 
55     // When the tileset is embedded we're effectively always saving it
56     savingTileset |= !tileset->isExternal();
57 
58     if (!savingTileset && !mOptions.testFlag(Preferences::EmbedTilesets))
59         return tileset; // Leave external tileset alone
60 
61     if (savingTileset && !(mOptions & (Preferences::DetachTemplateInstances |
62                                        Preferences::ResolveObjectTypesAndProperties))
63             && !hasExportSettings) {
64         // We're saving this tileset as-is, so leave it alone
65         return tileset;
66     }
67 
68     // Either needs to be embedded or is already embedded and we may need to
69     // make other changes to the tileset
70     SharedTileset exportTileset = tileset->clone();
71     exportTileset->setOriginalTileset(tileset);
72 
73     // We don't want to save the export options in the exported file
74     if (hasExportSettings) {
75         exportTileset->exportFileName.clear();
76         exportTileset->exportFormat.clear();
77     }
78 
79     if (mOptions.testFlag(Preferences::DetachTemplateInstances)) {
80         for (Tile *tile : exportTileset->tiles()) {
81             if (!tile->objectGroup())
82                 continue;
83 
84             for (MapObject *object : *tile->objectGroup())
85                 if (object->isTemplateInstance())
86                     object->detachFromTemplate();
87         }
88     }
89 
90     if (mOptions.testFlag(Preferences::ResolveObjectTypesAndProperties)) {
91         for (Tile *tile : exportTileset->tiles()) {
92             if (!tile->objectGroup())
93                 continue;
94 
95             for (MapObject *object : *tile->objectGroup())
96                 resolveTypeAndProperties(object);
97         }
98     }
99 
100     return exportTileset;
101 }
102 
prepareExportMap(const Map * map,std::unique_ptr<Map> & exportMap) const103 const Map *ExportHelper::prepareExportMap(const Map *map, std::unique_ptr<Map> &exportMap) const
104 {
105     const bool hasExportSettings = !(map->exportFileName.isEmpty()
106                                      && map->exportFormat.isEmpty());
107 
108     // If no export options are active, return the same map
109     if (!(mOptions & ~Preferences::ExportMinimized) && !hasExportSettings)
110         return map;
111 
112     // Make a copy to which export options are applied
113     exportMap = map->clone();
114 
115     // We don't want to save the export options in the exported file
116     if (hasExportSettings) {
117         exportMap->exportFileName.clear();
118         exportMap->exportFormat.clear();
119     }
120 
121     if (mOptions.testFlag(Preferences::DetachTemplateInstances)) {
122         for (Layer *layer : exportMap->objectGroups()) {
123             for (MapObject *object : *static_cast<ObjectGroup*>(layer)) {
124                 if (object->isTemplateInstance()) {
125                     // In case of templated tile objects, the map may not yet
126                     // have a reference to the used tileset.
127                     if (Tile *tile = object->cell().tile())
128                         exportMap->addTileset(tile->tileset()->sharedPointer());
129 
130                     object->detachFromTemplate();
131                 }
132             }
133         }
134     }
135 
136     if (mOptions.testFlag(Preferences::ResolveObjectTypesAndProperties))
137         for (Layer *layer : exportMap->objectGroups())
138             for (MapObject *object : *static_cast<ObjectGroup*>(layer))
139                 resolveTypeAndProperties(object);
140 
141     const auto tilesets = exportMap->tilesets();    // needs a copy
142     for (const SharedTileset &tileset : tilesets) {
143         auto exportTileset = prepareExportTileset(tileset, false);
144         if (exportTileset != tileset)
145             exportMap->replaceTileset(tileset, exportTileset);
146     }
147 
148     // Return a pointer to the copy
149     return exportMap.get();
150 }
151 
resolveTypeAndProperties(MapObject * object) const152 void ExportHelper::resolveTypeAndProperties(MapObject *object) const
153 {
154     Tile *tile = object->cell().tile();
155 
156     // Inherit type from tile if not set on object (not inheriting
157     // type from tile of tile object template here, for that the
158     // "Detach templates" option needs to be used as well)
159     if (object->type().isEmpty() && tile &&
160             (!object->isTemplateInstance() || object->propertyChanged(MapObject::CellProperty)))
161         object->setType(tile->type());
162 
163     Properties properties;
164 
165     // Inherit properties from type
166     if (!object->type().isEmpty()) {
167         for (int i = Object::objectTypes().size() - 1; i >= 0; --i) {
168             auto const &type = Object::objectTypes().at(i);
169             if (type.name == object->type())
170                 mergeProperties(properties, type.defaultProperties);
171         }
172     }
173 
174     // Inherit properties from tile
175     if (tile)
176         mergeProperties(properties, tile->properties());
177 
178     // Override with own properties
179     mergeProperties(properties, object->properties());
180 
181     object->setProperties(properties);
182 }
183 
184 } // namespace Tiled
185