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