1 /*
2    Drawpile - a collaborative drawing program.
3 
4    Copyright (C) 2013-2019 Calle Laakkonen
5 
6    Drawpile is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10 
11    Drawpile is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with Drawpile.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "layerlist.h"
21 #include "core/layer.h"
22 #include "../libshared/net/layer.h"
23 #include "aclfilter.h"
24 
25 #include <QDebug>
26 #include <QStringList>
27 #include <QBuffer>
28 #include <QImage>
29 #include <QRegularExpression>
30 
31 namespace canvas {
32 
LayerListModel(QObject * parent)33 LayerListModel::LayerListModel(QObject *parent)
34 	: QAbstractListModel(parent), m_aclfilter(nullptr), m_defaultLayer(0), m_myId(1)
35 {
36 }
37 
rowCount(const QModelIndex & parent) const38 int LayerListModel::rowCount(const QModelIndex &parent) const
39 {
40 	if(parent.isValid())
41 		return 0;
42 	return m_items.size();
43 }
44 
data(const QModelIndex & index,int role) const45 QVariant LayerListModel::data(const QModelIndex &index, int role) const
46 {
47 	if(index.isValid() && index.row() >= 0 && index.row() < m_items.size()) {
48 		const LayerListItem &item = m_items.at(index.row());
49 
50 		switch(role) {
51 		case Qt::DisplayRole: return QVariant::fromValue(item);
52 		case TitleRole:
53 		case Qt::EditRole: return item.title;
54 		case IdRole: return item.id;
55 		case IsDefaultRole: return item.id == m_defaultLayer;
56 		case IsLockedRole: return m_aclfilter && m_aclfilter->isLayerLocked(item.id);
57 		case IsFixedRole: return item.fixed;
58 		}
59 	}
60 	return QVariant();
61 }
62 
flags(const QModelIndex & index) const63 Qt::ItemFlags LayerListModel::flags(const QModelIndex& index) const
64 {
65 	if(!index.isValid())
66 		return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled;
67 
68 	return Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
69 }
70 
supportedDropActions() const71 Qt::DropActions LayerListModel::supportedDropActions() const
72 {
73 	return Qt::MoveAction;
74 }
75 
mimeTypes() const76 QStringList LayerListModel::mimeTypes() const {
77 		return QStringList() << "application/x-qt-image";
78 }
79 
mimeData(const QModelIndexList & indexes) const80 QMimeData *LayerListModel::mimeData(const QModelIndexList& indexes) const
81 {
82 	return new LayerMimeData(this, indexes[0].data().value<LayerListItem>().id);
83 }
84 
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)85 bool LayerListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
86 {
87 	Q_UNUSED(action);
88 	Q_UNUSED(column);
89 	Q_UNUSED(parent);
90 
91 	const LayerMimeData *ldata = qobject_cast<const LayerMimeData*>(data);
92 	if(ldata && ldata->source() == this) {
93 		// note: if row is -1, the item was dropped on the parent element, which in the
94 		// case of the list view means the empty area below the items.
95 		handleMoveLayer(indexOf(ldata->layerId()), row<0 ? m_items.count() : row);
96 	} else {
97 		// TODO support new layer drops
98 		qWarning() << "External layer drag&drop not supported";
99 	}
100 	return false;
101 }
102 
handleMoveLayer(int oldIdx,int newIdx)103 void LayerListModel::handleMoveLayer(int oldIdx, int newIdx)
104 {
105 	// Need at least two layers for this to make sense
106 	const int count = m_items.count();
107 	if(count < 2)
108 		return;
109 
110 	// If we're moving the layer to a higher index, take into
111 	// account that all previous indexes shift down by one.
112 	int adjustedNewIdx = newIdx > oldIdx ? newIdx - 1 : newIdx;
113 
114 	if(oldIdx < 0 || oldIdx >= count || adjustedNewIdx < 0 || adjustedNewIdx >= count) {
115 		// This can happen when a layer is deleted while someone is drag&dropping it
116 		qWarning("Whoops, can't move layer from %d to %d because it was just deleted!", oldIdx, newIdx);
117 		return;
118 	}
119 
120 	QList<uint16_t> layers;
121 	layers.reserve(count);
122 	for(const LayerListItem &li : m_items)
123 		layers.append(li.id);
124 
125 	layers.move(oldIdx, adjustedNewIdx);
126 
127 	// Layers are shown topmost first in the list but
128 	// are sent bottom first in the protocol.
129 	for(int i=0;i<count/2;++i) {
130 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
131 		layers.swap(i,count-(1+i));
132 #else
133 		layers.swapItemsAt(i,count-(1+i));
134 #endif
135 	}
136 
137 	emit layerCommand(protocol::MessagePtr(new protocol::LayerOrder(m_myId, layers)));
138 }
139 
indexOf(uint16_t id) const140 int LayerListModel::indexOf(uint16_t id) const
141 {
142 	for(int i=0;i<m_items.size();++i)
143 		if(m_items.at(i).id == id)
144 			return i;
145 	return -1;
146 }
147 
layerIndex(uint16_t id)148 QModelIndex LayerListModel::layerIndex(uint16_t id)
149 {
150 	int i = indexOf(id);
151 	if(i>=0)
152 		return index(i);
153 	return QModelIndex();
154 }
155 
createLayer(uint16_t id,int index,const QString & title)156 void LayerListModel::createLayer(uint16_t id, int index, const QString &title)
157 {
158 	beginInsertRows(QModelIndex(), index, index);
159 	m_items.insert(index, LayerListItem { id, title, 1.0, paintcore::BlendMode::MODE_NORMAL, false, false, false });
160 	endInsertRows();
161 }
162 
deleteLayer(uint16_t id)163 void LayerListModel::deleteLayer(uint16_t id)
164 {
165 	int row = indexOf(id);
166 	if(row<0)
167 		return;
168 
169 	beginRemoveRows(QModelIndex(), row, row);
170 	if(m_defaultLayer == id)
171 		m_defaultLayer = 0;
172 	m_items.remove(row);
173 	endRemoveRows();
174 }
175 
clear()176 void LayerListModel::clear()
177 {
178 	beginRemoveRows(QModelIndex(), 0, m_items.size());
179 	m_items.clear();
180 	m_defaultLayer = 0;
181 	endRemoveRows();
182 }
183 
changeLayer(uint16_t id,bool censored,bool fixed,float opacity,paintcore::BlendMode::Mode blend)184 void LayerListModel::changeLayer(uint16_t id, bool censored, bool fixed, float opacity, paintcore::BlendMode::Mode blend)
185 {
186 	int row = indexOf(id);
187 	if(row<0)
188 		return;
189 
190 	LayerListItem &item = m_items[row];
191 	item.opacity = opacity;
192 	item.blend = blend;
193 	item.censored = censored;
194 	item.fixed = fixed;
195 	const QModelIndex qmi = index(row);
196 	emit dataChanged(qmi, qmi);
197 }
198 
retitleLayer(uint16_t id,const QString & title)199 void LayerListModel::retitleLayer(uint16_t id, const QString &title)
200 {
201 	int row = indexOf(id);
202 	if(row<0)
203 		return;
204 
205 	LayerListItem &item = m_items[row];
206 	item.title = title;
207 	const QModelIndex qmi = index(row);
208 	emit dataChanged(qmi, qmi);
209 }
210 
setLayerHidden(uint16_t id,bool hidden)211 void LayerListModel::setLayerHidden(uint16_t id, bool hidden)
212 {
213 	int row = indexOf(id);
214 	if(row<0)
215 		return;
216 
217 	LayerListItem &item = m_items[row];
218 	item.hidden = hidden;
219 	const QModelIndex qmi = index(row);
220 	emit dataChanged(qmi, qmi);
221 }
222 
reorderLayers(QList<uint16_t> neworder)223 void LayerListModel::reorderLayers(QList<uint16_t> neworder)
224 {
225 	if(neworder.isEmpty()) {
226 		qWarning("reorderLayers(): empty layer list!");
227 		return;
228 	}
229 
230 	QVector<LayerListItem> newitems;
231 	for(int j=neworder.size()-1;j>=0;--j) {
232 		const uint16_t id=neworder[j];
233 		for(int i=0;i<m_items.size();++i) {
234 			if(m_items[i].id == id) {
235 				newitems << m_items[i];
236 				break;
237 			}
238 		}
239 	}
240 	m_items = newitems;
241 	emit dataChanged(index(0), index(m_items.size()-1));
242 	emit layersReordered();
243 }
244 
setLayers(const QVector<LayerListItem> & items)245 void LayerListModel::setLayers(const QVector<LayerListItem> &items)
246 {
247 	beginResetModel();
248 	m_items = items;
249 	endResetModel();
250 }
251 
setDefaultLayer(uint16_t id)252 void LayerListModel::setDefaultLayer(uint16_t id)
253 {
254 	const int oldIdx = indexOf(m_defaultLayer);
255 	if(oldIdx >= 0) {
256 		emit dataChanged(index(oldIdx), index(oldIdx), QVector<int>() << IsDefaultRole);
257 	}
258 
259 	m_defaultLayer = id;
260 	const int newIdx = indexOf(id);
261 	if(newIdx >= 0) {
262 		emit dataChanged(index(newIdx), index(newIdx), QVector<int>() << IsDefaultRole);
263 	}
264 }
265 
getLayerData(uint16_t id) const266 const paintcore::Layer *LayerListModel::getLayerData(uint16_t id) const
267 {
268 	if(m_getlayerfn)
269 		return m_getlayerfn(id);
270 	return nullptr;
271 }
272 
previewOpacityChange(uint16_t id,float opacity)273 void LayerListModel::previewOpacityChange(uint16_t id, float opacity)
274 {
275 	emit layerOpacityPreview(id, opacity);
276 }
277 
formats() const278 QStringList LayerMimeData::formats() const
279 {
280 	return QStringList() << "application/x-qt-image";
281 }
282 
retrieveData(const QString & mimeType,QVariant::Type type) const283 QVariant LayerMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const
284 {
285 	Q_UNUSED(mimeType);
286 	if(type==QVariant::Image) {
287 		const paintcore::Layer *layer = m_source->getLayerData(m_id);
288 		if(layer)
289 			return layer->toCroppedImage(nullptr, nullptr);
290 	}
291 
292 	return QVariant();
293 }
294 
getAvailableLayerId() const295 int LayerListModel::getAvailableLayerId() const
296 {
297 	const int prefix = m_myId << 8;
298 	QList<int> takenIds;
299 	for(const LayerListItem &item : m_items) {
300 		if((item.id & 0xff00) == prefix)
301 			takenIds.append(item.id);
302 	}
303 
304 	for(int i=0;i<256;++i) {
305 		int id = prefix | i;
306 		if(!takenIds.contains(id))
307 			return id;
308 	}
309 
310 	return 0;
311 }
312 
getAvailableLayerName(QString basename) const313 QString LayerListModel::getAvailableLayerName(QString basename) const
314 {
315 	// Return a layer name of format "basename n" where n is one bigger than the
316 	// biggest suffix number of layers named "basename n".
317 
318 	// First, strip suffix number from the basename (if it exists)
319 
320 	QRegularExpression suffixNumRe("(\\d+)$");
321 	{
322 		auto m = suffixNumRe.match(basename);
323 		if(m.hasMatch()) {
324 			basename = basename.mid(0, m.capturedStart()).trimmed();
325 		}
326 	}
327 
328 	// Find the biggest suffix in the layer stack
329 	int suffix = 0;
330 	for(const LayerListItem &l : m_items) {
331 		auto m = suffixNumRe.match(l.title);
332 		if(m.hasMatch()) {
333 			if(l.title.startsWith(basename)) {
334 				suffix = qMax(suffix, m.captured(1).toInt());
335 			}
336 		}
337 	}
338 
339 	// Make unique name
340 	return QString("%2 %1").arg(suffix+1).arg(basename);
341 }
342 
attributeFlags() const343 uint8_t LayerListItem::attributeFlags() const
344 {
345 	return (censored ? protocol::LayerAttributes::FLAG_CENSOR : 0) |
346 	       (fixed ? protocol::LayerAttributes::FLAG_FIXED : 0);
347 }
348 
349 }
350 
351