1 /*
2    Drawpile - a collaborative drawing program.
3 
4    Copyright (C) 2015-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 "canvasmodel.h"
21 #include "usercursormodel.h"
22 #include "lasertrailmodel.h"
23 #include "statetracker.h"
24 #include "layerlist.h"
25 #include "userlist.h"
26 #include "aclfilter.h"
27 #include "loader.h"
28 
29 #include "core/layerstack.h"
30 #include "core/annotationmodel.h"
31 #include "core/layer.h"
32 #include "ora/orawriter.h"
33 #include "utils/identicon.h"
34 #include "net/internalmsg.h"
35 
36 #include "../libshared/net/meta.h"
37 #include "../libshared/net/meta2.h"
38 #include "../libshared/net/recording.h"
39 
40 #include <QSettings>
41 #include <QDebug>
42 #include <QPainter>
43 
44 namespace canvas {
45 
CanvasModel(uint8_t localUserId,QObject * parent)46 CanvasModel::CanvasModel(uint8_t localUserId, QObject *parent)
47 	: QObject(parent), m_selection(nullptr), m_mode(Mode::Offline)
48 {
49 	m_layerlist = new LayerListModel(this);
50 	m_userlist = new UserListModel(this);
51 
52 	m_aclfilter = new AclFilter(this);
53 
54 	connect(m_aclfilter, &AclFilter::operatorListChanged, m_userlist, &UserListModel::updateOperators);
55 	connect(m_aclfilter, &AclFilter::trustedUserListChanged, m_userlist, &UserListModel::updateTrustedUsers);
56 	connect(m_aclfilter, &AclFilter::userLocksChanged, m_userlist, &UserListModel::updateLocks);
57 
58 	m_layerstack = new paintcore::LayerStack(this);
59 	m_statetracker = new StateTracker(m_layerstack, m_layerlist, localUserId, this);
60 	m_usercursors = new UserCursorModel(this);
61 	m_lasers = new LaserTrailModel(this);
62 
63 	m_aclfilter->reset(localUserId, true);
64 
65 	m_layerlist->setMyId(localUserId);
66 	m_layerlist->setAclFilter(m_aclfilter);
67 	m_layerlist->setLayerGetter([this](int id)->const paintcore::Layer* {
68 		return m_layerstack->getLayer(id);
69 	});
70 
71 	m_usercursors->setLayerList(m_layerlist);
72 
73 	connect(m_statetracker, &StateTracker::layerAutoselectRequest, this, &CanvasModel::layerAutoselectRequest);
74 
75 	connect(m_statetracker, &StateTracker::userMarkerMove, m_usercursors, &UserCursorModel::setCursorPosition);
76 	connect(m_statetracker, &StateTracker::userMarkerHide, m_usercursors, &UserCursorModel::hideCursor);
77 
78 	connect(m_layerstack, &paintcore::LayerStack::resized, this, &CanvasModel::onCanvasResize);
79 
80 	updateLayerViewOptions();
81 }
82 
localUserId() const83 uint8_t CanvasModel::localUserId() const
84 {
85 	return m_statetracker->localId();
86 }
87 
connectedToServer(uint8_t myUserId,bool join)88 void CanvasModel::connectedToServer(uint8_t myUserId, bool join)
89 {
90 	Q_ASSERT(m_mode == Mode::Offline);
91 	m_layerlist->setMyId(myUserId);
92 	m_statetracker->setLocalId(myUserId);
93 
94 	if(join)
95 		m_aclfilter->reset(myUserId, false);
96 	else
97 		m_aclfilter->setOnlineMode(myUserId);
98 
99 	m_userlist->reset();
100 	m_mode = Mode::Online;
101 }
102 
disconnectedFromServer()103 void CanvasModel::disconnectedFromServer()
104 {
105 	m_statetracker->endRemoteContexts();
106 	m_userlist->allLogout();
107 	m_aclfilter->reset(m_statetracker->localId(), true);
108 	m_mode = Mode::Offline;
109 }
110 
startPlayback()111 void CanvasModel::startPlayback()
112 {
113 	Q_ASSERT(m_mode == Mode::Offline);
114 	m_mode = Mode::Playback;
115 	m_statetracker->setShowAllUserMarkers(true);
116 }
117 
endPlayback()118 void CanvasModel::endPlayback()
119 {
120 	Q_ASSERT(m_mode == Mode::Playback);
121 	m_statetracker->setShowAllUserMarkers(false);
122 	m_statetracker->endPlayback();
123 }
124 
handleCommand(protocol::MessagePtr cmd)125 void CanvasModel::handleCommand(protocol::MessagePtr cmd)
126 {
127 	using namespace protocol;
128 
129 	if(cmd->type() == protocol::MSG_INTERNAL) {
130 		m_statetracker->receiveQueuedCommand(cmd);
131 		return;
132 	}
133 
134 	// Apply ACL filter
135 	if(m_mode != Mode::Playback && !m_aclfilter->filterMessage(*cmd)) {
136 		qWarning("Filtered %s message from %d", qPrintable(cmd->messageName()), cmd->contextId());
137 		if(m_recorder)
138 			m_recorder->recordMessage(cmd->asFiltered());
139 		return;
140 
141 	} else if(m_recorder) {
142 		m_recorder->recordMessage(cmd);
143 	}
144 
145 	if(cmd->isMeta()) {
146 		// Handle meta commands here
147 		switch(cmd->type()) {
148 		case MSG_CHAT:
149 			metaChatMessage(cmd);
150 			break;
151 		case MSG_PRIVATE_CHAT:
152 			emit chatMessageReceived(cmd);
153 			break;
154 		case MSG_USER_JOIN:
155 			metaUserJoin(cmd.cast<UserJoin>());
156 			break;
157 		case MSG_USER_LEAVE:
158 			metaUserLeave(cmd.cast<UserLeave>());
159 			break;
160 		case MSG_SESSION_OWNER:
161 		case MSG_TRUSTED_USERS:
162 		case MSG_USER_ACL:
163 		case MSG_FEATURE_LEVELS:
164 		case MSG_LAYER_ACL:
165 			// Handled by the ACL filter
166 			break;
167 		case MSG_INTERVAL:
168 		case MSG_FILTERED:
169 			// recording playback related messages
170 			break;
171 		case MSG_LASERTRAIL:
172 			metaLaserTrail(cmd.cast<protocol::LaserTrail>());
173 			break;
174 		case MSG_MOVEPOINTER:
175 			metaMovePointer(cmd.cast<MovePointer>());
176 			break;
177 		case MSG_MARKER:
178 			metaMarkerMessage(cmd.cast<Marker>());
179 			break;
180 		case MSG_LAYER_DEFAULT:
181 			metaDefaultLayer(cmd.cast<DefaultLayer>());
182 			break;
183 		case MSG_SOFTRESET:
184 			metaSoftReset(cmd->contextId());
185 			break;
186 		default:
187 			qWarning("Unhandled meta message %s", qPrintable(cmd->messageName()));
188 		}
189 
190 	} else if(cmd->isCommand()) {
191 		// The state tracker handles all drawing commands
192 		m_statetracker->receiveQueuedCommand(cmd);
193 		emit canvasModified();
194 
195 	} else {
196 		qWarning("CanvasModel::handleDrawingCommand: command %d is neither Meta nor Command type!", cmd->type());
197 	}
198 }
199 
handleLocalCommand(protocol::MessagePtr cmd)200 void CanvasModel::handleLocalCommand(protocol::MessagePtr cmd)
201 {
202 	m_statetracker->localCommand(cmd);
203 	emit canvasModified();
204 }
205 
toImage(bool withBackground,bool withSublayers) const206 QImage CanvasModel::toImage(bool withBackground, bool withSublayers) const
207 {
208 	// TODO include annotations or not?
209 	return m_layerstack->toFlatImage(false, withBackground, withSublayers);
210 }
211 
needsOpenRaster() const212 bool CanvasModel::needsOpenRaster() const
213 {
214 	return m_layerstack->layerCount() > 1 ||
215 		!m_layerstack->annotations()->isEmpty() ||
216 		!m_layerstack->background().isBlank()
217 		;
218 }
219 
generateSnapshot() const220 protocol::MessageList CanvasModel::generateSnapshot() const
221 {
222 	auto loader = SnapshotLoader(m_statetracker->localId(), m_layerstack, m_aclfilter);
223 	loader.setDefaultLayer(m_layerlist->defaultLayer());
224 	loader.setPinnedMessage(m_pinnedMessage);
225 	return loader.loadInitCommands();
226 }
227 
pickLayer(int x,int y)228 void CanvasModel::pickLayer(int x, int y)
229 {
230 	const paintcore::Layer *l = m_layerstack->layerAt(x, y);
231 	if(l) {
232 		emit layerAutoselectRequest(l->id());
233 	}
234 }
235 
pickColor(int x,int y,int layer,int diameter)236 void CanvasModel::pickColor(int x, int y, int layer, int diameter)
237 {
238 	QColor color;
239 	if(layer>0) {
240 		const paintcore::Layer *l = m_layerstack->getLayer(layer);
241 		if(layer)
242 			color = l->colorAt(x, y, diameter);
243 	} else {
244 		color = m_layerstack->colorAt(x, y, diameter);
245 	}
246 
247 	if(color.isValid() && color.alpha()>0) {
248 		color.setAlpha(255);
249 		emit colorPicked(color);
250 	}
251 }
252 
inspectCanvas(int x,int y)253 void CanvasModel::inspectCanvas(int x, int y)
254 {
255 	if(x>=0 && y>=0 && x<m_layerstack->width() && y<m_layerstack->height()) {
256 		const int tx = x / paintcore::Tile::SIZE;
257 		const int ty = y / paintcore::Tile::SIZE;
258 		const int id = m_layerstack->tileLastEditedBy(tx, ty);
259 		inspectCanvas(id);
260 		emit canvasInspected(tx, ty, id);
261 	}
262 }
263 
inspectCanvas(int contextId)264 void CanvasModel::inspectCanvas(int contextId)
265 {
266 	m_layerstack->editor(0).setInspectorHighlight(contextId);
267 }
268 
stopInspectingCanvas()269 void CanvasModel::stopInspectingCanvas()
270 {
271 	m_layerstack->editor(0).setInspectorHighlight(0);
272 	emit canvasInspectionEnded();
273 }
274 
setLayerViewMode(int mode)275 void CanvasModel::setLayerViewMode(int mode)
276 {
277 	m_layerstack->editor(0).setViewMode(paintcore::LayerStack::ViewMode(mode));
278 	updateLayerViewOptions();
279 }
280 
setSelection(Selection * selection)281 void CanvasModel::setSelection(Selection *selection)
282 {
283 	if(m_selection != selection) {
284 		m_layerstack->editor(0).removePreviews();
285 
286 		const bool hadSelection = m_selection != nullptr;
287 
288 		if(hadSelection && m_selection->parent() == this)
289 			m_selection->deleteLater();
290 
291 		if(selection && !selection->parent())
292 			selection->setParent(this);
293 
294 		m_selection = selection;
295 
296 		emit selectionChanged(selection);
297 		if(hadSelection && !selection)
298 			emit selectionRemoved();
299 	}
300 }
301 
updateLayerViewOptions()302 void CanvasModel::updateLayerViewOptions()
303 {
304 	QSettings cfg;
305 	cfg.beginGroup("settings/animation");
306 	m_layerstack->editor(0).setOnionskinMode(
307 		cfg.value("onionskinsbelow", 4).toInt(),
308 		cfg.value("onionskinsabove", 4).toInt(),
309 		cfg.value("onionskintint", true).toBool()
310 	);
311 }
312 
313 /**
314  * @brief Find an unused annotation ID
315  *
316  * Find an annotation ID (for this user) that is currently not in use.
317  * @return available ID or 0 if none found
318  */
getAvailableAnnotationId() const319 uint16_t CanvasModel::getAvailableAnnotationId() const
320 {
321 	const uint16_t prefix = uint16_t(m_statetracker->localId() << 8);
322 	QList<uint16_t> takenIds;
323 	for(const paintcore::Annotation &a : m_layerstack->annotations()->getAnnotations()) {
324 		if((a.id & 0xff00) == prefix)
325 				takenIds << a.id;
326 	}
327 
328 	for(uint16_t i=0;i<256;++i) {
329 		uint16_t id = prefix | i;
330 		if(!takenIds.contains(id))
331 			return id;
332 	}
333 
334 	return 0;
335 }
336 
selectionToImage(int layerId) const337 QImage CanvasModel::selectionToImage(int layerId) const
338 {
339 	QImage img;
340 
341 	if(m_selection && !m_selection->pasteImage().isNull()) {
342 		return m_selection->transformedPasteImage();
343 	}
344 
345 	const paintcore::Layer *layer = m_layerstack->getLayer(layerId);
346 	if(layer)
347 		img = layer->toImage();
348 	else
349 		img = toImage(layerId==0);
350 
351 
352 	if(m_selection) {
353 		img = img.copy(m_selection->boundingRect().intersected(QRect(0, 0, img.width(), img.height())));
354 
355 		if(!m_selection->isAxisAlignedRectangle()) {
356 			// Mask out pixels outside the selection
357 			QPainter mp(&img);
358 			mp.setCompositionMode(QPainter::CompositionMode_DestinationIn);
359 
360 			QRect maskBounds;
361 			const QImage mask = m_selection->shapeMask(Qt::white, &maskBounds);
362 
363 			mp.drawImage(qMin(0, maskBounds.left()), qMin(0, maskBounds.top()), mask);
364 		}
365 	}
366 
367 	return img;
368 }
369 
pasteFromImage(const QImage & image,const QPoint & defaultPoint,bool forceDefault)370 void CanvasModel::pasteFromImage(const QImage &image, const QPoint &defaultPoint, bool forceDefault)
371 {
372 	QPoint center;
373 	if(m_selection && !forceDefault)
374 		center = m_selection->boundingRect().center();
375 	else
376 		center = defaultPoint;
377 
378 	Selection *paste = new Selection;
379 	paste->setShapeRect(QRect(center.x() - image.width()/2, center.y() - image.height()/2, image.width(), image.height()));
380 	paste->setPasteImage(image);
381 
382 	setSelection(paste);
383 }
384 
onCanvasResize(int xoffset,int yoffset,const QSize & oldsize)385 void CanvasModel::onCanvasResize(int xoffset, int yoffset, const QSize &oldsize)
386 {
387 	Q_UNUSED(oldsize);
388 
389 	// Adjust selection when new space was added to the left or top side
390 	// so it remains visually in the same place
391 	if(m_selection) {
392 		if(xoffset || yoffset) {
393 			QPoint offset(xoffset, yoffset);
394 			m_selection->translate(offset);
395 		}
396 	}
397 }
398 
resetCanvas()399 void CanvasModel::resetCanvas()
400 {
401 	setTitle(QString());
402 	m_layerstack->editor(0).reset();
403 	m_statetracker->reset();
404 	m_aclfilter->reset(m_statetracker->localId(), false);
405 }
406 
metaUserJoin(const protocol::UserJoin & msg)407 void CanvasModel::metaUserJoin(const protocol::UserJoin &msg)
408 {
409 	QImage avatar;
410 	if(!msg.avatar().isEmpty()) {
411 		QByteArray avatarData = msg.avatar();
412 		if(!avatar.loadFromData(avatarData))
413 			qWarning("Avatar loading failed for user '%s' (#%d)", qPrintable(msg.name()), msg.contextId());
414 
415 		// Rescale avatar if its the wrong size
416 		if(avatar.width() > 32 || avatar.height() > 32) {
417 			avatar = avatar.scaled(32, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation);
418 		}
419 	}
420 	if(avatar.isNull())
421 		avatar = make_identicon(msg.name());
422 
423 	const User u {
424 		msg.contextId(),
425 		msg.name(),
426 		QPixmap::fromImage(avatar),
427 		msg.contextId() == m_statetracker->localId(),
428 		false,
429 		false,
430 		msg.isModerator(),
431 		msg.isBot(),
432 		msg.isAuthenticated(),
433 		false,
434 		false,
435 		true
436 	};
437 
438 	m_userlist->userLogin(u);
439 	m_usercursors->setCursorName(msg.contextId(), msg.name());
440 	m_usercursors->setCursorAvatar(msg.contextId(), u.avatar);
441 
442 	emit userJoined(msg.contextId(), msg.name());
443 }
444 
metaUserLeave(const protocol::UserLeave & msg)445 void CanvasModel::metaUserLeave(const protocol::UserLeave &msg)
446 {
447 	const QString name = m_userlist->getUsername(msg.contextId());
448 	m_userlist->userLogout(msg.contextId());
449 	emit userLeft(msg.contextId(), name);
450 }
451 
metaChatMessage(protocol::MessagePtr msg)452 void CanvasModel::metaChatMessage(protocol::MessagePtr msg)
453 {
454 	Q_ASSERT(msg->type() == protocol::MSG_CHAT);
455 	const protocol::Chat &chat = msg.cast<protocol::Chat>();
456 	if(chat.isPin()) {
457 		QString pm = chat.message();
458 		if(m_pinnedMessage != pm) {
459 			if(pm == "-") // special value to remove a pinned message
460 				pm = QString();
461 			m_pinnedMessage = pm;
462 			emit pinnedMessageChanged(pm);
463 		}
464 	}
465 	emit chatMessageReceived(msg);
466 }
467 
metaLaserTrail(const protocol::LaserTrail & msg)468 void CanvasModel::metaLaserTrail(const protocol::LaserTrail &msg)
469 {
470 	m_lasers->startTrail(msg.contextId(), QColor::fromRgb(msg.color()), msg.persistence());
471 }
472 
metaMovePointer(const protocol::MovePointer & msg)473 void CanvasModel::metaMovePointer(const protocol::MovePointer &msg)
474 {
475 	QPoint p(int(msg.x() / 4.0), int(msg.y() / 4.0));
476 	m_usercursors->setCursorPosition(msg.contextId(), 0, p);
477 	m_lasers->addPoint(msg.contextId(), p);
478 }
479 
metaMarkerMessage(const protocol::Marker & msg)480 void CanvasModel::metaMarkerMessage(const protocol::Marker &msg)
481 {
482 	emit markerMessageReceived(msg.contextId(), msg.text());
483 }
484 
metaDefaultLayer(const protocol::DefaultLayer & msg)485 void CanvasModel::metaDefaultLayer(const protocol::DefaultLayer &msg)
486 {
487 	m_layerlist->setDefaultLayer(msg.layer());
488 	if(!m_statetracker->hasParticipated())
489 		emit layerAutoselectRequest(msg.layer());
490 }
491 
metaSoftReset(uint8_t resetterId)492 void CanvasModel::metaSoftReset(uint8_t resetterId)
493 {
494 	m_statetracker->receiveQueuedCommand(protocol::ClientInternal::makeTruncatePoint());
495 
496 	if(resetterId == localUserId())
497 		m_statetracker->receiveQueuedCommand(protocol::ClientInternal::makeSoftResetPoint());
498 }
499 
500 }
501