1 /*
2  * wangsetmodel.cpp
3  * Copyright 2017, Benjamin Trotter <bdtrotte@ucsc.edu>
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 "wangsetmodel.h"
22 
23 #include "changeevents.h"
24 #include "containerhelpers.h"
25 #include "map.h"
26 #include "mapdocument.h"
27 #include "tile.h"
28 #include "tileset.h"
29 #include "tilesetdocument.h"
30 #include "tilesetdocumentsmodel.h"
31 #include "wangoverlay.h"
32 #include "wangset.h"
33 
34 #include <QApplication>
35 #include <QFont>
36 #include <QPalette>
37 
38 using namespace Tiled;
39 
WangSetModel(QAbstractItemModel * tilesetDocumentModel,QObject * parent)40 WangSetModel::WangSetModel(QAbstractItemModel *tilesetDocumentModel,
41                            QObject *parent):
42     QAbstractItemModel(parent),
43     mTilesetDocumentsModel(tilesetDocumentModel)
44 {
45     connect(mTilesetDocumentsModel, &QAbstractItemModel::rowsInserted,
46             this, &WangSetModel::onTilesetRowsInserted);
47     connect(mTilesetDocumentsModel, &QAbstractItemModel::rowsAboutToBeRemoved,
48             this, &WangSetModel::onTilesetRowsAboutToBeRemoved);
49     connect(mTilesetDocumentsModel, &QAbstractItemModel::rowsMoved,
50             this, &WangSetModel::onTilesetRowsMoved);
51     connect(mTilesetDocumentsModel, &QAbstractItemModel::layoutChanged,
52             this, &WangSetModel::onTilesetLayoutChanged);
53     connect(mTilesetDocumentsModel, &QAbstractItemModel::dataChanged,
54             this, &WangSetModel::onTilesetDataChanged);
55 }
56 
~WangSetModel()57 WangSetModel::~WangSetModel()
58 {
59 }
60 
index(int row,int column,const QModelIndex & parent) const61 QModelIndex WangSetModel::index(int row, int column, const QModelIndex &parent) const
62 {
63     if (!hasIndex(row, column, parent))
64         return QModelIndex();
65 
66     if (!parent.isValid())
67         return createIndex(row, column);
68     else if (Tileset *tileset = tilesetAt(parent))
69         return createIndex(row, column, tileset);
70 
71     return QModelIndex();
72 }
73 
index(Tileset * tileset) const74 QModelIndex WangSetModel::index(Tileset *tileset) const
75 {
76     for (int row = 0; row < mTilesetDocuments.size(); ++row)
77         if (mTilesetDocuments.at(row)->tileset() == tileset)
78             return createIndex(row, 0);
79 
80     return QModelIndex();
81 }
82 
index(WangSet * wangSet) const83 QModelIndex WangSetModel::index(WangSet *wangSet) const
84 {
85     Tileset *tileset = wangSet->tileset();
86     int row = tileset->wangSets().indexOf(wangSet);
87     return createIndex(row, 0, tileset);
88 }
89 
parent(const QModelIndex & child) const90 QModelIndex WangSetModel::parent(const QModelIndex &child) const
91 {
92     if (WangSet *wangSet = wangSetAt(child))
93         return index(wangSet->tileset());
94 
95     return QModelIndex();
96 }
97 
rowCount(const QModelIndex & parent) const98 int WangSetModel::rowCount(const QModelIndex &parent) const
99 {
100     if (!parent.isValid())
101         return mTilesetDocuments.size();
102     else if (Tileset *tileset = tilesetAt(parent))
103         return tileset->wangSetCount();
104 
105     return 0;
106 }
107 
columnCount(const QModelIndex & parent) const108 int WangSetModel::columnCount(const QModelIndex &parent) const
109 {
110     Q_UNUSED(parent)
111     return 1;
112 }
113 
data(const QModelIndex & index,int role) const114 QVariant WangSetModel::data(const QModelIndex &index, int role) const
115 {
116     if (WangSet *wangSet = wangSetAt(index)) {
117         switch (role) {
118         case Qt::DisplayRole:
119         case Qt::EditRole:
120             return wangSet->name();
121         case Qt::DecorationRole:
122             if (Tile *tile = wangSet->imageTile())
123                 return tile->image();
124             else
125                 return wangSetIcon(wangSet->type());
126             break;
127         case WangSetRole:
128             return QVariant::fromValue(wangSet);
129         }
130     } else if (Tileset *tileset = tilesetAt(index)) {
131         switch (role) {
132         case Qt::DisplayRole:
133             return tileset->name();
134         case Qt::SizeHintRole:
135             return QSize(1, 32);
136         case Qt::FontRole: {
137             QFont font = QApplication::font();
138             font.setBold(true);
139             return font;
140         }
141         case Qt::BackgroundRole: {
142             QColor bg = QApplication::palette().alternateBase().color();
143             return bg;//.darker(103);
144         }
145         }
146     }
147 
148     return QVariant();
149 }
150 
flags(const QModelIndex & index) const151 Qt::ItemFlags WangSetModel::flags(const QModelIndex &index) const
152 {
153     Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
154 
155     if (tilesetAt(index))
156         defaultFlags &= ~Qt::ItemIsSelectable;
157 
158     return defaultFlags;
159 }
160 
tilesetAt(const QModelIndex & index) const161 Tileset *WangSetModel::tilesetAt(const QModelIndex &index) const
162 {
163     if (!index.isValid())
164         return nullptr;
165     if (index.parent().isValid()) // tilesets don't have parents
166         return nullptr;
167     if (index.row() >= mTilesetDocuments.size())
168         return nullptr;
169 
170     return mTilesetDocuments.at(index.row())->tileset().data();
171 }
172 
wangSetAt(const QModelIndex & index) const173 WangSet *WangSetModel::wangSetAt(const QModelIndex &index) const
174 {
175     if (!index.isValid())
176         return nullptr;
177 
178     if (Tileset *tileset = static_cast<Tileset*>(index.internalPointer()))
179         return tileset->wangSet(index.row());
180 
181     return nullptr;
182 }
183 
onTilesetRowsInserted(const QModelIndex & parent,int first,int last)184 void WangSetModel::onTilesetRowsInserted(const QModelIndex &parent, int first, int last)
185 {
186     beginInsertRows(QModelIndex(), first, last);
187     for (int row = first; row <= last; ++row) {
188         const QModelIndex index = mTilesetDocumentsModel->index(row, 0, parent);
189         const QVariant var = mTilesetDocumentsModel->data(index, TilesetDocumentsModel::TilesetDocumentRole);
190         TilesetDocument *tilesetDocument = var.value<TilesetDocument*>();
191 
192         mTilesetDocuments.insert(row, tilesetDocument);
193 
194         connect(tilesetDocument, &Document::changed, this, &WangSetModel::onDocumentChanged);
195     }
196     endInsertRows();
197 }
198 
onTilesetRowsAboutToBeRemoved(const QModelIndex & parent,int first,int last)199 void WangSetModel::onTilesetRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
200 {
201     Q_UNUSED(parent)
202 
203     beginRemoveRows(QModelIndex(), first, last);
204     for (int index = last; index >= first; --index) {
205         TilesetDocument *tilesetDocument = mTilesetDocuments.takeAt(index);
206         tilesetDocument->disconnect(this);
207     }
208     endRemoveRows();
209 }
210 
onTilesetRowsMoved(const QModelIndex & parent,int start,int end,const QModelIndex & destination,int row)211 void WangSetModel::onTilesetRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
212 {
213     Q_UNUSED(parent)
214     Q_UNUSED(destination)
215 
216     beginMoveRows(QModelIndex(), start, end, QModelIndex(), row);
217 
218     if (start == row)
219         return;
220 
221     while (start <= end) {
222         mTilesetDocuments.move(start, row);
223 
224         if (row < start) {
225             ++start;
226             ++row;
227         } else {
228             --end;
229         }
230     }
231 
232     endMoveRows();
233 }
234 
onTilesetLayoutChanged(const QList<QPersistentModelIndex> & parents,QAbstractItemModel::LayoutChangeHint hint)235 void WangSetModel::onTilesetLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
236 {
237     Q_UNUSED(parents)
238     Q_UNUSED(hint)
239 
240     // Make sure the tileset documents are still in the right order
241     for (int i = 0, rows = mTilesetDocuments.size(); i < rows; ++i) {
242         const QModelIndex index = mTilesetDocumentsModel->index(i, 0);
243         const QVariant var = mTilesetDocumentsModel->data(index, TilesetDocumentsModel::TilesetDocumentRole);
244         TilesetDocument *tilesetDocument = var.value<TilesetDocument*>();
245         int currentIndex = mTilesetDocuments.indexOf(tilesetDocument);
246         if (currentIndex != i) {
247             Q_ASSERT(currentIndex > i);
248             onTilesetRowsMoved(QModelIndex(), currentIndex, currentIndex, QModelIndex(), i);
249         }
250     }
251 }
252 
onTilesetDataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight)253 void WangSetModel::onTilesetDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
254 {
255     emit dataChanged(index(topLeft.row(), topLeft.column()),
256                      index(bottomRight.row(), bottomRight.column()));
257 }
258 
onDocumentChanged(const ChangeEvent & change)259 void WangSetModel::onDocumentChanged(const ChangeEvent &change)
260 {
261     switch (change.type) {
262     case ChangeEvent::WangSetAboutToBeAdded: {
263         auto wangSetEvent = static_cast<const WangSetEvent&>(change);
264 
265         QModelIndex parent = this->index(wangSetEvent.tileset);
266         beginInsertRows(parent, wangSetEvent.index, wangSetEvent.index);
267         break;
268     }
269     case ChangeEvent::WangSetAdded: {
270         auto wangSetEvent = static_cast<const WangSetEvent&>(change);
271 
272         endInsertRows();
273 
274         const QModelIndex index = WangSetModel::index(wangSetEvent.tileset);
275         emit dataChanged(index, index);
276         break;
277     }
278 
279     case ChangeEvent::WangSetAboutToBeRemoved: {
280         auto wangSetEvent = static_cast<const WangSetEvent&>(change);
281         auto wangSet = wangSetEvent.tileset->wangSet(wangSetEvent.index);
282 
283         QModelIndex parent = index(wangSetEvent.tileset);
284         beginRemoveRows(parent, index(wangSet).row(), index(wangSet).row());
285         break;
286     }
287     case ChangeEvent::WangSetRemoved: {
288         auto wangSetEvent = static_cast<const WangSetEvent&>(change);
289 
290         endRemoveRows();
291 
292         const QModelIndex index = WangSetModel::index(wangSetEvent.tileset);
293         emit dataChanged(index, index);
294         break;
295     }
296     default:
297         break;
298     }
299 }
300 
301 #include "moc_wangsetmodel.cpp"
302