1 /*
2  * tilestampmodel.cpp
3  * Copyright 2015, 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 "tilestampmodel.h"
22 
23 #include "minimaprenderer.h"
24 
25 namespace Tiled {
26 
TileStampModel(QObject * parent)27 TileStampModel::TileStampModel(QObject *parent)
28     : QAbstractItemModel(parent)
29 {
30 }
31 
index(int row,int column,const QModelIndex & parent) const32 QModelIndex TileStampModel::index(int row, int column, const QModelIndex &parent) const
33 {
34     if (!hasIndex(row, column, parent))
35         return QModelIndex();
36 
37     if (!parent.isValid())
38         return createIndex(row, column);
39     else if (isStamp(parent))
40         return createIndex(row, column, parent.row() + 1);
41 
42     return QModelIndex();
43 }
44 
parent(const QModelIndex & index) const45 QModelIndex TileStampModel::parent(const QModelIndex &index) const
46 {
47     if (quintptr id = index.internalId())
48         return createIndex(id - 1, 0);
49 
50     return QModelIndex();
51 }
52 
rowCount(const QModelIndex & parent) const53 int TileStampModel::rowCount(const QModelIndex &parent) const
54 {
55     if (!parent.isValid()) {
56         return mStamps.size();
57     } else if (isStamp(parent)) {
58         const TileStamp &stamp = mStamps.at(parent.row());
59         const int count = stamp.variations().size();
60         // it does not make much sense to expand single variations
61         return count == 1 ? 0 : count;
62     }
63 
64     return 0;
65 }
66 
columnCount(const QModelIndex & parent) const67 int TileStampModel::columnCount(const QModelIndex &parent) const
68 {
69     Q_UNUSED(parent)
70     return 2; // stamp | probability
71 }
72 
headerData(int section,Qt::Orientation orientation,int role) const73 QVariant TileStampModel::headerData(int section, Qt::Orientation orientation, int role) const
74 {
75     if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
76         switch (section) {
77         case 0: return tr("Stamp");
78         case 1: return tr("Probability");
79         }
80     }
81     return QVariant();
82 }
83 
setData(const QModelIndex & index,const QVariant & value,int role)84 bool TileStampModel::setData(const QModelIndex &index, const QVariant &value, int role)
85 {
86     if (isStamp(index)) {
87         TileStamp &stamp = mStamps[index.row()];
88         if (index.column() == 0) {      // stamp name
89             switch (role) {
90             case Qt::EditRole:
91                 stamp.setName(value.toString());
92                 emit dataChanged(index, index);
93                 emit stampRenamed(stamp);
94                 emit stampChanged(stamp);
95                 return true;
96             default:
97                 break;
98             }
99         }
100     } else if (index.column() == 1) {   // variation probability
101         QModelIndex parent = index.parent();
102         if (isStamp(parent)) {
103             TileStamp &stamp = mStamps[parent.row()];
104             stamp.setProbability(index.row(), value.toReal());
105             emit dataChanged(index, index);
106 
107             QModelIndex probabilitySumIndex = TileStampModel::index(parent.row(), 1);
108             emit dataChanged(probabilitySumIndex, probabilitySumIndex);
109 
110             emit stampChanged(stamp);
111             return true;
112         }
113     }
114 
115     return false;
116 }
117 
renderThumbnail(const MiniMapRenderer & renderer)118 static QPixmap renderThumbnail(const MiniMapRenderer &renderer)
119 {
120     const MiniMapRenderer::RenderFlags renderFlags(MiniMapRenderer::DrawMapObjects |
121                                                    MiniMapRenderer::DrawImageLayers |
122                                                    MiniMapRenderer::DrawTileLayers |
123                                                    MiniMapRenderer::IgnoreInvisibleLayer |
124                                                    MiniMapRenderer::SmoothPixmapTransform |
125                                                    MiniMapRenderer::IncludeOverhangingTiles);
126 
127     return QPixmap::fromImage(renderer.render(QSize(64, 64), renderFlags)
128                               .scaled(32, 32,
129                                       Qt::IgnoreAspectRatio,
130                                       Qt::SmoothTransformation));
131 }
132 
data(const QModelIndex & index,int role) const133 QVariant TileStampModel::data(const QModelIndex &index, int role) const
134 {
135     if (isStamp(index)) {
136         const TileStamp &stamp = mStamps.at(index.row());
137         if (index.column() == 0) {          // preview and name
138             switch (role) {
139             case Qt::DisplayRole:
140             case Qt::EditRole:
141                 return stamp.name();
142             case Qt::DecorationRole: {
143                 Map *map = stamp.variations().first().map;
144                 QPixmap thumbnail = mThumbnailCache.value(map);
145                 if (thumbnail.isNull()) {
146                     MiniMapRenderer renderer(map);
147                     thumbnail = renderThumbnail(renderer);
148                     mThumbnailCache.insert(map, thumbnail);
149                 }
150                 return thumbnail;
151             }
152             }
153         } else if (index.column() == 1) {   // sum of probabilities
154             switch (role) {
155             case Qt::DisplayRole:
156                 if (stamp.variations().size() > 1) {
157                     qreal sum = 0;
158                     for (const TileStampVariation &variation : stamp.variations())
159                         sum += variation.probability;
160                     return sum;
161                 }
162             }
163         }
164     } else if (const TileStampVariation *variation = variationAt(index)) {
165         if (index.column() == 0) {
166             switch (role) {
167             case Qt::DecorationRole: {
168                 Map *map = variation->map;
169                 QPixmap thumbnail = mThumbnailCache.value(map);
170                 if (thumbnail.isNull()) {
171                     MiniMapRenderer renderer(map);
172                     thumbnail = renderThumbnail(renderer);
173                     mThumbnailCache.insert(map, thumbnail);
174                 }
175                 return thumbnail;
176             }
177             }
178         } else if (index.column() == 1) {
179             switch (role) {
180             case Qt::DisplayRole:
181             case Qt::EditRole:
182                 return variation->probability;
183             }
184         }
185     }
186 
187     return QVariant();
188 }
189 
flags(const QModelIndex & index) const190 Qt::ItemFlags TileStampModel::flags(const QModelIndex &index) const
191 {
192     Qt::ItemFlags rc = QAbstractItemModel::flags(index);
193     const bool validParent = index.parent().isValid();
194     if ((!validParent && index.column() == 0) ||   // can edit stamp names
195             (validParent && index.column() == 1))  // and variation probability
196         rc |= Qt::ItemIsEditable;
197     return rc;
198 }
199 
removeRows(int row,int count,const QModelIndex & parent)200 bool TileStampModel::removeRows(int row, int count, const QModelIndex &parent)
201 {
202     if (parent.isValid()) {
203         // removing variations
204         TileStamp &stamp = mStamps[parent.row()];
205 
206         // if only one variation is left, we make all variation rows disappear
207         if (stamp.variations().size() - count == 1)
208             beginRemoveRows(parent, 0, count);
209         else
210             beginRemoveRows(parent, row, row + count - 1);
211 
212         for (; count > 0; --count) {
213             mThumbnailCache.remove(stamp.variations().at(row).map);
214             delete stamp.takeVariation(row);
215         }
216         endRemoveRows();
217 
218         if (stamp.variations().isEmpty()) {
219             // remove stamp since all its variations were removed
220             beginRemoveRows(QModelIndex(), parent.row(), parent.row());
221             emit stampRemoved(stamp);
222             mStamps.removeAt(parent.row());
223             endRemoveRows();
224         } else {
225             if (row == 0) {
226                 // preview on stamp and probability sum need update
227                 // (while technically I think this is correct, it triggers a
228                 // repainting issue in QTreeView)
229                 //emit dataChanged(index(parent.row(), 0),
230                 //                 index(parent.row(), 1));
231             }
232             emit stampChanged(stamp);
233         }
234     } else {
235         // removing stamps
236         beginRemoveRows(parent, row, row + count - 1);
237         for (; count > 0; --count) {
238             for (const TileStampVariation &variation : mStamps.at(row).variations())
239                 mThumbnailCache.remove(variation.map);
240             emit stampRemoved(mStamps.at(row));
241             mStamps.removeAt(row);
242         }
243         endRemoveRows();
244     }
245 
246     return true;
247 }
248 
stampAt(const QModelIndex & index) const249 const TileStamp &TileStampModel::stampAt(const QModelIndex &index) const
250 {
251     Q_ASSERT(index.isValid());
252     Q_ASSERT(!index.parent().isValid()); // stamps don't have parents
253 
254     return mStamps.at(index.row());
255 }
256 
isStamp(const QModelIndex & index) const257 bool TileStampModel::isStamp(const QModelIndex &index) const
258 {
259     return index.isValid()
260             && !index.parent().isValid()
261             && index.row() < mStamps.size();
262 }
263 
variationAt(const QModelIndex & index) const264 const TileStampVariation *TileStampModel::variationAt(const QModelIndex &index) const
265 {
266     if (!index.isValid())
267         return nullptr;
268 
269     QModelIndex parent = index.parent();
270     if (isStamp(parent)) {
271         const TileStamp &stamp = mStamps.at(parent.row());
272         return &stamp.variations().at(index.row());
273     }
274 
275     return nullptr;
276 }
277 
addStamp(const TileStamp & stamp)278 void TileStampModel::addStamp(const TileStamp &stamp)
279 {
280     if (mStamps.contains(stamp))
281         return;
282 
283     beginInsertRows(QModelIndex(), mStamps.size(), mStamps.size());
284     mStamps.append(stamp);
285     emit stampAdded(stamp);
286     endInsertRows();
287 }
288 
removeStamp(const TileStamp & stamp)289 void TileStampModel::removeStamp(const TileStamp &stamp)
290 {
291     int index = mStamps.indexOf(stamp);
292     if (index == -1)
293         return;
294 
295     beginRemoveRows(QModelIndex(), index, index);
296     mStamps.removeAt(index);
297     endRemoveRows();
298 
299     for (const TileStampVariation &variation : stamp.variations())
300         mThumbnailCache.remove(variation.map);
301 
302     emit stampRemoved(stamp);
303 }
304 
addVariation(const TileStamp & stamp,const TileStampVariation & variation)305 void TileStampModel::addVariation(const TileStamp &stamp,
306                                   const TileStampVariation &variation)
307 {
308     int index = mStamps.indexOf(stamp);
309     if (index == -1)
310         return;
311 
312     const int variationCount = stamp.variations().size();
313 
314     if (variationCount == 1)
315         beginInsertRows(TileStampModel::index(index, 0), 0, 1);
316     else
317         beginInsertRows(TileStampModel::index(index, 0),
318                         variationCount, variationCount);
319 
320     mStamps[index].addVariation(variation);
321     endInsertRows();
322 
323     QModelIndex probabilitySumIndex = TileStampModel::index(index, 1);
324     emit dataChanged(probabilitySumIndex, probabilitySumIndex);
325 
326     emit stampChanged(stamp);
327 }
328 
clear()329 void TileStampModel::clear()
330 {
331     beginResetModel();
332     mStamps.clear();
333     mThumbnailCache.clear();
334     endResetModel();
335 }
336 
337 } // namespace Tiled
338 
339 #include "moc_tilestampmodel.cpp"
340