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