1 /*
2 * abstracttiletool.cpp
3 * Copyright 2009-2010, Thorbjørn Lindeijer <thorbjorn@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 "abstracttiletool.h"
22
23 #include "brushitem.h"
24 #include "map.h"
25 #include "mapdocument.h"
26 #include "maprenderer.h"
27 #include "mapscene.h"
28 #include "tile.h"
29 #include "tilelayer.h"
30 #include "tilestamp.h"
31
32 #include <QKeyEvent>
33 #include <QtMath>
34
35 using namespace Tiled;
36
AbstractTileTool(Id id,const QString & name,const QIcon & icon,const QKeySequence & shortcut,BrushItem * brushItem,QObject * parent)37 AbstractTileTool::AbstractTileTool(Id id,
38 const QString &name,
39 const QIcon &icon,
40 const QKeySequence &shortcut,
41 BrushItem *brushItem,
42 QObject *parent)
43 : AbstractTool(id, name, icon, shortcut, parent)
44 , mTilePositionMethod(OnTiles)
45 , mBrushItem(brushItem)
46 , mBrushVisible(false)
47 {
48 if (!mBrushItem)
49 mBrushItem = new BrushItem;
50 mBrushItem->setVisible(false);
51 mBrushItem->setZValue(10000);
52 }
53
~AbstractTileTool()54 AbstractTileTool::~AbstractTileTool()
55 {
56 delete mBrushItem;
57 }
58
activate(MapScene * scene)59 void AbstractTileTool::activate(MapScene *scene)
60 {
61 scene->addItem(mBrushItem);
62 AbstractTool::activate(scene);
63 }
64
deactivate(MapScene * scene)65 void AbstractTileTool::deactivate(MapScene *scene)
66 {
67 scene->removeItem(mBrushItem);
68 AbstractTool::deactivate(scene);
69 }
70
mouseEntered()71 void AbstractTileTool::mouseEntered()
72 {
73 setBrushVisible(true);
74 }
75
mouseLeft()76 void AbstractTileTool::mouseLeft()
77 {
78 setBrushVisible(false);
79 }
80
mouseMoved(const QPointF & pos,Qt::KeyboardModifiers)81 void AbstractTileTool::mouseMoved(const QPointF &pos, Qt::KeyboardModifiers)
82 {
83 // Take into account the offset of the current layer
84 QPointF offsetPos = pos;
85 if (Layer *layer = currentLayer()) {
86 QPointF layerOffset = mapScene()->absolutePositionForLayer(*layer);
87 offsetPos -= layerOffset;
88 mBrushItem->setLayerOffset(layerOffset);
89 }
90
91 const MapRenderer *renderer = mapDocument()->renderer();
92 const QPointF tilePosF = renderer->screenToTileCoords(offsetPos);
93 QPoint tilePos;
94
95 if (mTilePositionMethod == BetweenTiles)
96 tilePos = tilePosF.toPoint();
97 else
98 tilePos = QPoint(qFloor(tilePosF.x()),
99 qFloor(tilePosF.y()));
100
101 if (mTilePosition != tilePos) {
102 mTilePosition = tilePos;
103
104 tilePositionChanged(tilePos);
105 updateStatusInfo();
106 }
107 }
108
mousePressed(QGraphicsSceneMouseEvent * event)109 void AbstractTileTool::mousePressed(QGraphicsSceneMouseEvent *event)
110 {
111 if (event->button() == Qt::RightButton && event->modifiers() & Qt::ControlModifier) {
112 const QPointF mousePos = event->pos();
113 const MapRenderer *renderer = mapDocument()->renderer();
114
115 QList<Layer*> layers;
116
117 const bool append = event->modifiers() & Qt::ShiftModifier;
118 const bool selectAll = event->modifiers() & Qt::AltModifier;
119
120 if (append)
121 layers = mapDocument()->selectedLayers();
122
123 LayerIterator it(mapDocument()->map(), Layer::TileLayerType);
124 it.toBack();
125 while (auto tileLayer = static_cast<TileLayer*>(it.previous())) {
126 if (tileLayer->isHidden())
127 continue;
128
129 const QPointF layerOffset = mapScene()->absolutePositionForLayer(*tileLayer);
130 const QPointF tilePosF = renderer->screenToTileCoords(mousePos - layerOffset);
131 const QPoint tilePos = QPoint(qFloor(tilePosF.x()),
132 qFloor(tilePosF.y()));
133
134 if (!tileLayer->cellAt(tilePos - tileLayer->position()).isEmpty()) {
135 if (!layers.contains(tileLayer))
136 layers.append(tileLayer);
137 else if (append)
138 layers.removeOne(tileLayer);
139
140 if (!selectAll)
141 break;
142 }
143 }
144
145 if (!layers.isEmpty())
146 mapDocument()->switchSelectedLayers(layers);
147
148 return;
149 }
150
151 event->ignore();
152 }
153
mapDocumentChanged(MapDocument * oldDocument,MapDocument * newDocument)154 void AbstractTileTool::mapDocumentChanged(MapDocument *oldDocument,
155 MapDocument *newDocument)
156 {
157 Q_UNUSED(oldDocument)
158 mBrushItem->setMapDocument(newDocument);
159 }
160
updateEnabledState()161 void AbstractTileTool::updateEnabledState()
162 {
163 setEnabled(currentTileLayer() != nullptr);
164 updateBrushVisibility();
165 }
166
updateStatusInfo()167 void AbstractTileTool::updateStatusInfo()
168 {
169 if (mBrushVisible) {
170 Cell cell;
171
172 if (const TileLayer *tileLayer = currentTileLayer()) {
173 const QPoint pos = tilePosition() - tileLayer->position();
174 cell = tileLayer->cellAt(pos);
175 }
176
177 QString tileIdString = cell.tileId() >= 0 ? QString::number(cell.tileId()) : tr("empty");
178
179 QVarLengthArray<QChar, 3> flippedBits;
180 if (cell.flippedHorizontally())
181 flippedBits.append(QLatin1Char('H'));
182 if (cell.flippedVertically())
183 flippedBits.append(QLatin1Char('V'));
184 if (cell.flippedAntiDiagonally())
185 flippedBits.append(QLatin1Char('D'));
186
187 if (!flippedBits.isEmpty()) {
188 tileIdString.append(QLatin1Char(' '));
189 tileIdString.append(flippedBits.first());
190 for (int i = 1; i < flippedBits.size(); ++i) {
191 tileIdString.append(QLatin1Char(','));
192 tileIdString.append(flippedBits.at(i));
193 }
194 }
195
196 setStatusInfo(QStringLiteral("%1, %2 [%3]")
197 .arg(mTilePosition.x())
198 .arg(mTilePosition.y())
199 .arg(tileIdString));
200 } else {
201 setStatusInfo(QString());
202 }
203 }
204
currentTileLayer() const205 TileLayer *AbstractTileTool::currentTileLayer() const
206 {
207 if (mapDocument())
208 if (auto currentLayer = mapDocument()->currentLayer())
209 return currentLayer->asTileLayer();
210 return nullptr;
211 }
212
updateBrushVisibility()213 void AbstractTileTool::updateBrushVisibility()
214 {
215 // Show the tile brush only when at least one target layer is visible
216 bool showBrush = false;
217 if (mBrushVisible) {
218 const auto layers = targetLayers();
219 for (auto layer : layers) {
220 if (!layer->isHidden()) {
221 showBrush = true;
222 break;
223 }
224 }
225 }
226 mBrushItem->setVisible(showBrush);
227 }
228
targetLayers() const229 QList<Layer *> AbstractTileTool::targetLayers() const
230 {
231 // By default, only a current tile layer is considered the target
232 QList<Layer *> layers;
233 if (Layer *layer = currentTileLayer())
234 layers.append(layer);
235 return layers;
236 }
237
238 /**
239 * A helper method that returns the possible target layers of a given \a stamp.
240 */
targetLayersForStamp(const TileStamp & stamp) const241 QList<Layer *> AbstractTileTool::targetLayersForStamp(const TileStamp &stamp) const
242 {
243 QList<Layer*> layers;
244
245 if (!mapDocument())
246 return layers;
247
248 const Map &map = *mapDocument()->map();
249
250 for (const TileStampVariation &variation : stamp.variations()) {
251 LayerIterator it(variation.map, Layer::TileLayerType);
252 const Layer *firstLayer = it.next();
253 const bool isMultiLayer = firstLayer && it.next();
254
255 if (isMultiLayer && !firstLayer->name().isEmpty()) {
256 for (Layer *layer : variation.map->tileLayers()) {
257 if (TileLayer *target = static_cast<TileLayer*>(map.findLayer(layer->name(), Layer::TileLayerType)))
258 if (!layers.contains(target))
259 layers.append(target);
260 }
261 } else {
262 if (TileLayer *tileLayer = currentTileLayer())
263 if (!layers.contains(tileLayer))
264 layers.append(tileLayer);
265 }
266 }
267
268 return layers;
269 }
270
setBrushVisible(bool visible)271 void AbstractTileTool::setBrushVisible(bool visible)
272 {
273 if (mBrushVisible == visible)
274 return;
275
276 mBrushVisible = visible;
277 updateStatusInfo();
278 updateBrushVisibility();
279 }
280
281 #include "moc_abstracttiletool.cpp"
282