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