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