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 "statetracker.h"
21 #include "canvasmodel.h"
22 #include "layerlist.h"
23 #include "loader.h"
24
25 #include "core/layerstack.h"
26 #include "core/layer.h"
27 #include "brushes/brushpainter.h"
28 #include "net/commands.h"
29 #include "net/internalmsg.h"
30 #include "tools/selection.h" // for selection transform utils
31
32 #include "../libshared/net/brushes.h"
33 #include "../libshared/net/layer.h"
34 #include "../libshared/net/image.h"
35 #include "../libshared/net/annotation.h"
36 #include "../libshared/net/undo.h"
37
38 #include <QDebug>
39 #include <QDateTime>
40 #include <QTimer>
41 #include <QElapsedTimer>
42 #include <QSettings>
43 #include <QPainter>
44
45 namespace canvas {
46
47 struct StateSavepoint::Data : public QSharedData {
48 int streampointer = 0;
49 qint64 timestamp = 0;
50 paintcore::Savepoint canvas;
51 QVector<LayerListItem> layermodel;
52 };
53
StateSavepoint()54 StateSavepoint::StateSavepoint()
55 {
56 }
57
StateSavepoint(Data * d)58 StateSavepoint::StateSavepoint(Data *d)
59 : d(d)
60 {
61 }
62
StateSavepoint(const StateSavepoint & other)63 StateSavepoint::StateSavepoint(const StateSavepoint &other)
64 : d(other.d)
65 {
66 }
67
operator =(const StateSavepoint & other)68 StateSavepoint &StateSavepoint::operator=(const StateSavepoint &other)
69 {
70 d = other.d;
71 return *this;
72 }
73
~StateSavepoint()74 StateSavepoint::~StateSavepoint()
75 {
76 }
77
timestamp() const78 qint64 StateSavepoint::timestamp() const
79 {
80 return d ? d->timestamp : 0;
81 }
82
canvas() const83 paintcore::Savepoint StateSavepoint::canvas() const
84 {
85 Q_ASSERT(d);
86 return d->canvas;
87 }
88
thumbnail(const QSize & maxSize) const89 QImage StateSavepoint::thumbnail(const QSize &maxSize) const
90 {
91 if(!d)
92 return QImage();
93
94 paintcore::LayerStack stack;
95 stack.editor(0).restoreSavepoint(d->canvas);
96 QImage img = stack.toFlatImage(true, true, false);
97 if(img.width() > maxSize.width() || img.height() > maxSize.height()) {
98 img = img.scaled(maxSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
99 }
100 return img;
101 }
102
initCommands(uint8_t contextId,const CanvasModel * canvas) const103 protocol::MessageList StateSavepoint::initCommands(uint8_t contextId, const CanvasModel *canvas) const
104 {
105 if(!d)
106 return protocol::MessageList();
107
108 paintcore::LayerStack stack;
109 stack.editor(0).restoreSavepoint(d->canvas);
110 SnapshotLoader loader(contextId, &stack, canvas->aclFilter());
111 loader.setDefaultLayer(canvas->layerlist()->defaultLayer());
112 loader.setPinnedMessage(canvas->pinnedMessage());
113 return loader.loadInitCommands();
114 }
115
fromCanvasSavepoint(const paintcore::Savepoint & savepoint)116 StateSavepoint StateSavepoint::fromCanvasSavepoint(const paintcore::Savepoint &savepoint)
117 {
118 auto *d = new StateSavepoint::Data;
119
120 d->timestamp = QDateTime::currentMSecsSinceEpoch();
121 d->canvas = savepoint;
122
123 for(const paintcore::Layer *l : savepoint.layers) {
124 d->layermodel << LayerListItem {
125 uint16_t(l->id()),
126 l->title(),
127 l->opacity() / 255.0f,
128 l->blendmode(),
129 l->isHidden(),
130 l->isCensored(),
131 l->isFixed()
132 };
133 }
134 return StateSavepoint(d);
135 }
136
137 /**
138 * @brief Construct a state tracker instance
139 *
140 * @param image the canvas content
141 * @param layerlist layer list model for the UI
142 * @param myId ID of the local user
143 * @param parent
144 */
StateTracker(paintcore::LayerStack * image,LayerListModel * layerlist,uint8_t myId,QObject * parent)145 StateTracker::StateTracker(paintcore::LayerStack *image, LayerListModel *layerlist, uint8_t myId, QObject *parent)
146 : QObject(parent),
147 m_layerstack(image),
148 m_layerlist(layerlist),
149 m_myId(myId),
150 m_myLastLayer(-1),
151 _showallmarkers(false),
152 m_hasParticipated(false),
153 m_localPenDown(false),
154 m_isQueued(false)
155 {
156 connect(m_layerlist, &LayerListModel::layerOpacityPreview, this, &StateTracker::previewLayerOpacity);
157
158 // Reset local fork if it falls behind too much
159 m_localfork.setFallbehind(10000);
160
161 // Timer for processing drawing commands in short chunks to avoid entirely locking up the UI.
162 // In the future, canvas rendering should be done in a separate thread.
163 m_queuetimer = new QTimer(this);
164 m_queuetimer->setSingleShot(true);
165 connect(m_queuetimer, &QTimer::timeout, this, &StateTracker::processQueuedCommands);
166
167 // Ensure that there is always at least one save point
168 makeSavepoint(-1);
169 }
170
~StateTracker()171 StateTracker::~StateTracker()
172 {
173 }
174
reset()175 void StateTracker::reset()
176 {
177 m_savepoints.clear();
178 m_history.resetTo(m_history.end());
179 m_hasParticipated = false;
180 m_localPenDown = false;
181 m_msgqueue.clear();
182 m_localfork.clear();
183 m_layerlist->clear();
184
185 // Make sure there is always a savepoint in the history
186 makeSavepoint(m_history.end()-1);
187 }
188
localCommand(protocol::MessagePtr msg)189 void StateTracker::localCommand(protocol::MessagePtr msg)
190 {
191 // A fork is created at the end of the mainline history
192 if(m_localfork.isEmpty()) {
193 m_localfork.setOffset(m_history.end()-1);
194
195 // Since the presence of a local fork blocks savepoint creation,
196 // now is a good time to try to create one.
197 if(msg->type() == protocol::MSG_UNDOPOINT)
198 makeSavepoint(m_history.end()-1);
199 }
200
201 m_localfork.addLocalMessage(msg, affectedArea(msg));
202
203 // Remember last used layer
204 switch(msg->type()) {
205 using namespace protocol;
206 case MSG_DRAWDABS_CLASSIC:
207 case MSG_DRAWDABS_PIXEL:
208 case MSG_DRAWDABS_PIXEL_SQUARE:
209 case MSG_LAYER_CREATE:
210 case MSG_PUTIMAGE:
211 case MSG_FILLRECT:
212 case MSG_REGION_MOVE:
213 m_myLastLayer = msg->layer();
214 break;
215 default: break;
216 }
217
218 // for the future: handle undo messages in the local fork too
219 if(msg->type() != protocol::MSG_UNDO && msg->type() != protocol::MSG_UNDOPOINT) {
220 int pos = m_history.end() - 1;
221 handleCommand(msg, false, pos);
222 }
223 }
224
receiveQueuedCommand(protocol::MessagePtr msg)225 void StateTracker::receiveQueuedCommand(protocol::MessagePtr msg)
226 {
227 m_msgqueue.append(msg);
228
229 if(!m_isQueued) {
230 // This introduces a tiny bit of lag, but allows sequential
231 // messages to queue up even when the system is not under very heavy
232 // load. Won't be needed anymore when the paint engine runs in its own
233 // thread.
234 m_isQueued = true;
235 m_queuetimer->start(1);
236 }
237 }
238
processQueuedCommands()239 void StateTracker::processQueuedCommands()
240 {
241 QElapsedTimer elapsed;
242 elapsed.start();
243
244 while(!m_msgqueue.isEmpty() && elapsed.elapsed() < 100) {
245 receiveCommand(m_msgqueue.takeFirst());
246 }
247
248 if(!m_msgqueue.isEmpty()) {
249 qDebug("Taking a breather. Still %d messages in the queue.", m_msgqueue.size());
250 m_isQueued = true;
251 m_queuetimer->start(20);
252 } else {
253 m_isQueued = false;
254 }
255 }
256
receiveCommand(protocol::MessagePtr msg)257 void StateTracker::receiveCommand(protocol::MessagePtr msg)
258 {
259 if(msg->type() == protocol::MSG_INTERNAL) {
260 // MSG_INTERNAL is a pseudo-message used for internal synchronization
261 const auto &ci = msg.cast<protocol::ClientInternal>();
262 switch(ci.internalType()) {
263 case protocol::ClientInternal::Type::Catchup:
264 emit catchupProgress(ci.value());
265 break;
266 case protocol::ClientInternal::Type::SequencePoint:
267 emit sequencePoint(ci.value());
268 break;
269 case protocol::ClientInternal::Type::TruncateHistory:
270 handleTruncateHistory();
271 break;
272 case protocol::ClientInternal::Type::SoftResetPoint:
273 emit softResetPoint();
274 break;
275 }
276 return;
277 }
278
279 // Add command to history and execute it
280 m_history.append(msg);
281
282 LocalFork::MessageAction lfa = m_localfork.handleReceivedMessage(msg, affectedArea(msg));
283
284 // Undo messages are not handled locally (at the moment)
285 if(lfa == LocalFork::ALREADYDONE && (msg->type()==protocol::MSG_UNDO || msg->type()==protocol::MSG_UNDOPOINT))
286 lfa = LocalFork::CONCURRENT;
287
288 if(lfa==LocalFork::ROLLBACK) {
289 // Uh oh! An inconsistency was detected: roll back the history and replay
290
291 // first, find the newest savepoint that precedes the fork
292 int savepoint = m_savepoints.size()-1;
293 while(savepoint>=0) {
294 if(m_savepoints.at(savepoint)->streampointer <= m_localfork.offset())
295 break;
296 --savepoint;
297 }
298
299 if(savepoint<0) {
300 // should never happen
301 qWarning("No savepoint for rolling back local fork at %d!", m_localfork.offset());
302
303 } else {
304 const StateSavepoint &sp = m_savepoints.at(savepoint);
305 qDebug("inconsistency at %d (local fork at %d). Rolling back to %d", m_history.end(), m_localfork.offset(), sp->streampointer);
306
307 // Avoid rollback churn by clearing the local fork, but not if
308 // local drawing is in progress. If we clear the fork then,
309 // we trigger a self-conflict feedback loop until the stroke finishes.
310 if(!m_localPenDown)
311 m_localfork.clear();
312
313 revertSavepointAndReplay(sp);
314 }
315
316 } else if(lfa==LocalFork::CONCURRENT) {
317 // Concurrent operation: safe to execute
318 int pos = m_history.end() - 1;
319 handleCommand(msg, false, pos);
320 } // else ALREADYDONE
321 }
322
handleCommand(protocol::MessagePtr msg,bool replay,int pos)323 void StateTracker::handleCommand(protocol::MessagePtr msg, bool replay, int pos)
324 {
325 switch(msg->type()) {
326 using namespace protocol;
327 case MSG_CANVAS_RESIZE:
328 handleCanvasResize(msg.cast<CanvasResize>(), pos);
329 break;
330 case MSG_LAYER_CREATE:
331 handleLayerCreate(msg.cast<LayerCreate>());
332 break;
333 case MSG_LAYER_ATTR:
334 handleLayerAttributes(msg.cast<LayerAttributes>());
335 break;
336 case MSG_LAYER_VISIBILITY:
337 handleLayerVisibility(msg.cast<LayerVisibility>());
338 break;
339 case MSG_LAYER_RETITLE:
340 handleLayerTitle(msg.cast<LayerRetitle>());
341 break;
342 case MSG_LAYER_ORDER:
343 handleLayerOrder(msg.cast<LayerOrder>());
344 break;
345 case MSG_LAYER_DELETE:
346 handleLayerDelete(msg.cast<LayerDelete>());
347 break;
348 case MSG_DRAWDABS_CLASSIC:
349 case MSG_DRAWDABS_PIXEL:
350 case MSG_DRAWDABS_PIXEL_SQUARE:
351 handleDrawDabs(*msg);
352 break;
353 case MSG_PEN_UP:
354 handlePenUp(msg.cast<PenUp>());
355 break;
356 case MSG_PUTIMAGE:
357 handlePutImage(msg.cast<PutImage>());
358 break;
359 case MSG_UNDOPOINT:
360 handleUndoPoint(msg.cast<UndoPoint>(), replay, pos);
361 break;
362 case MSG_UNDO:
363 handleUndo(msg.cast<Undo>());
364 break;
365 case MSG_ANNOTATION_CREATE:
366 handleAnnotationCreate(msg.cast<AnnotationCreate>());
367 break;
368 case MSG_ANNOTATION_RESHAPE:
369 handleAnnotationReshape(msg.cast<AnnotationReshape>());
370 break;
371 case MSG_ANNOTATION_EDIT:
372 handleAnnotationEdit(msg.cast<AnnotationEdit>());
373 break;
374 case MSG_ANNOTATION_DELETE:
375 handleAnnotationDelete(msg.cast<AnnotationDelete>());
376 break;
377 case MSG_FILLRECT:
378 handleFillRect(msg.cast<FillRect>());
379 break;
380 case MSG_REGION_MOVE:
381 handleMoveRegion(msg.cast<MoveRegion>());
382 break;
383 case MSG_PUTTILE:
384 handlePutTile(msg.cast<PutTile>());
385 break;
386 case MSG_CANVAS_BACKGROUND:
387 handleCanvasBackground(msg.cast<CanvasBackground>());
388 break;
389 default:
390 qWarning() << "Unhandled drawing command" << msg->type() << msg->messageName();
391 return;
392 }
393 }
394
395 /**
396 * @brief Network disconnected, so end remote drawing processes
397 */
endRemoteContexts()398 void StateTracker::endRemoteContexts()
399 {
400 // Add local fork to the mainline history
401 auto localfork = m_localfork.messages();
402 m_localfork.clear();
403
404 for(protocol::MessagePtr m : localfork)
405 m_history.append(m);
406
407 // Make sure there are no lingering indirect strokes
408 // TODO this should probably be done with an InternalMsg,
409 // in case there is still stuff in the queue
410 auto layers = m_layerstack->editor(0);
411 layers.mergeAllSublayers();
412
413 m_myLastLayer = -1;
414 }
415
416 /**
417 * @brief Playback ended, make sure drawing contexts (local one included) are ended
418 */
endPlayback()419 void StateTracker::endPlayback()
420 {
421 auto layers = m_layerstack->editor(0);
422 layers.mergeAllSublayers();
423 }
424
425
426
handleCanvasResize(const protocol::CanvasResize & cmd,int pos)427 void StateTracker::handleCanvasResize(const protocol::CanvasResize &cmd, int pos)
428 {
429 {
430 auto layers = m_layerstack->editor(cmd.contextId());
431 layers.resize(cmd.top(), cmd.right(), cmd.bottom(), cmd.left());
432 }
433
434 // Generate the initial savepoint, just in case
435 makeSavepoint(pos);
436 }
437
handleCanvasBackground(const protocol::CanvasBackground & cmd)438 void StateTracker::handleCanvasBackground(const protocol::CanvasBackground &cmd)
439 {
440 paintcore::Tile t;
441 if(cmd.isSolidColor()) {
442 t = paintcore::Tile(QColor::fromRgba(cmd.color()));
443
444 } else {
445 QByteArray data = qUncompress(cmd.image());
446 if(data.length() != paintcore::Tile::BYTES) {
447 qWarning() << "Invalid canvas background: Expected" << paintcore::Tile::BYTES << "bytes, but got" << data.length();
448 return;
449 }
450
451 t = paintcore::Tile(data);
452 }
453 t.setLastEditedBy(cmd.contextId());
454 m_layerstack->editor(cmd.contextId()).setBackground(t);
455 }
456
handleLayerCreate(const protocol::LayerCreate & cmd)457 void StateTracker::handleLayerCreate(const protocol::LayerCreate &cmd)
458 {
459 auto layers = m_layerstack->editor(cmd.contextId());
460
461 auto layer = layers.createLayer(
462 cmd.layer(),
463 cmd.source(),
464 QColor::fromRgba(cmd.fill()),
465 (cmd.flags() & protocol::LayerCreate::FLAG_INSERT),
466 (cmd.flags() & protocol::LayerCreate::FLAG_COPY),
467 cmd.title()
468 );
469
470 if(layer.isNull()) {
471 qWarning("Layer creation failed (id=%d, source=%d)", cmd.layer(), cmd.source());
472 return;
473 }
474
475 // Note: layers are listed bottom-first in the stack,
476 // but topmost first in the view
477 m_layerlist->createLayer(
478 cmd.layer(),
479 layers->layerCount() - layers->indexOf(layer->id()) - 1,
480 cmd.title()
481 );
482
483 // Auto-select layers we create
484 // During the startup phase, autoselect new layers or if a default one is set,
485 // just the default one. If there is a remembered layer selection, it takes precedence
486 // over others.
487 if(
488 // Autoselect layers created by me
489 (m_hasParticipated && cmd.contextId() == localId()) ||
490 // If this user has not yet drawn anything...
491 (!m_hasParticipated && (
492 // ... and if there is no remembered layer...
493 ((m_myLastLayer <= 0) && ( // ...select default layer or if not selected, any new layer
494 layer->id() == m_layerlist->defaultLayer() ||
495 !m_layerlist->defaultLayer()
496 )) ||
497 // ... and if there is a remembered layer, select only that one
498 (m_myLastLayer>0 && layer->id() == m_myLastLayer)
499 ))
500 )
501 {
502 emit layerAutoselectRequest(layer->id());
503 }
504 }
505
handleLayerAttributes(const protocol::LayerAttributes & cmd)506 void StateTracker::handleLayerAttributes(const protocol::LayerAttributes &cmd)
507 {
508 auto layers = m_layerstack->editor(cmd.contextId());
509 auto layer = layers.getEditableLayer(cmd.layer());
510 if(layer.isNull()) {
511 qWarning("Received layer attributes for non-existent layer #%d", cmd.layer());
512 return;
513 }
514
515 const auto bm = paintcore::BlendMode::Mode(cmd.blend());
516
517 if(cmd.sublayer()>0) {
518 auto sl = layer.getEditableSubLayer(cmd.sublayer(), bm, cmd.opacity());
519 // getSubLayer does not touch the attributes if the sublayer already exists
520 sl.setBlend(bm);
521 sl.setOpacity(cmd.opacity());
522
523 } else {
524 layer.setBlend(bm);
525 layer.setOpacity(cmd.opacity());
526 layer.setCensored(cmd.isCensored());
527 layer.setFixed(cmd.isFixed());
528 m_layerlist->changeLayer(layer->id(), cmd.isCensored(), cmd.isFixed(), cmd.opacity() / 255.0, paintcore::BlendMode::Mode(cmd.blend()));
529 }
530 }
531
handleLayerVisibility(const protocol::LayerVisibility & cmd)532 void StateTracker::handleLayerVisibility(const protocol::LayerVisibility &cmd)
533 {
534 // Layer visibility affects the sending user only
535 // (to hide a layer from all users, one can just set its opacity to zero.)
536 if(cmd.contextId() != localId())
537 return;
538
539 auto layers = m_layerstack->editor(cmd.contextId());
540 auto layer = layers.getEditableLayer(cmd.layer());
541 if(layer.isNull()) {
542 qWarning("Received layer visibility for non-existent layer #%d", cmd.layer());
543 return;
544 }
545
546 layer.setHidden(!cmd.visible());
547 m_layerlist->setLayerHidden(layer->id(), !cmd.visible());
548 }
549
previewLayerOpacity(int id,float opacity)550 void StateTracker::previewLayerOpacity(int id, float opacity)
551 {
552 auto layers = m_layerstack->editor(0);
553 auto layer = layers.getEditableLayer(id);
554
555 if(layer.isNull()) {
556 qWarning("previewLayerOpacity(%d): no such layer!", id);
557 return;
558 }
559 layer.setOpacity(opacity*255);
560 }
561
handleLayerTitle(const protocol::LayerRetitle & cmd)562 void StateTracker::handleLayerTitle(const protocol::LayerRetitle &cmd)
563 {
564 auto layers = m_layerstack->editor(cmd.contextId());
565 auto layer = layers.getEditableLayer(cmd.layer());
566
567 if(layer.isNull()) {
568 qWarning() << "received layer title for non-existent layer" << cmd.layer();
569 return;
570 }
571
572 layer.setTitle(cmd.title());
573 m_layerlist->retitleLayer(layer->id(), cmd.title());
574 }
575
handleLayerOrder(const protocol::LayerOrder & cmd)576 void StateTracker::handleLayerOrder(const protocol::LayerOrder &cmd)
577 {
578 auto layers = m_layerstack->editor(cmd.contextId());
579
580 QList<uint16_t> currentOrder;
581 for(int i=0;i<layers->layerCount();++i)
582 currentOrder.append(layers->getLayerByIndex(i)->id());
583
584 QList<uint16_t> newOrder = cmd.sanitizedOrder(currentOrder);
585
586 if(newOrder != cmd.order()) {
587 qWarning() << "invalid layer reorder!";
588 qWarning() << "current order is:" << currentOrder;
589 qWarning() << " the new one was:" << cmd.order();
590 qWarning() << " fixed order is:" << newOrder;
591 }
592
593 layers.reorderLayers(newOrder);
594 m_layerlist->reorderLayers(newOrder);
595 }
596
handleLayerDelete(const protocol::LayerDelete & cmd)597 void StateTracker::handleLayerDelete(const protocol::LayerDelete &cmd)
598 {
599 auto layers = m_layerstack->editor(cmd.contextId());
600
601 if(cmd.merge())
602 layers.mergeLayerDown(cmd.layer());
603 layers.deleteLayer(cmd.layer());
604 m_layerlist->deleteLayer(cmd.layer());
605 }
606
handleDrawDabs(const protocol::Message & cmd)607 void StateTracker::handleDrawDabs(const protocol::Message &cmd)
608 {
609 auto layers = m_layerstack->editor(cmd.contextId());
610
611 brushes::drawBrushDabs(cmd, layers);
612
613 if(_showallmarkers || cmd.contextId() != localId())
614 emit userMarkerMove(cmd.contextId(), cmd.layer(), static_cast<const protocol::DrawDabs&>(cmd).lastPoint());
615 }
616
handlePenUp(const protocol::PenUp & cmd)617 void StateTracker::handlePenUp(const protocol::PenUp &cmd)
618 {
619 // This ends an indirect stroke. In incremental mode, this does nothing.
620 m_layerstack->editor(cmd.contextId()).mergeSublayers(cmd.contextId());
621 emit userMarkerHide(cmd.contextId());
622 }
623
handlePutImage(const protocol::PutImage & cmd)624 void StateTracker::handlePutImage(const protocol::PutImage &cmd)
625 {
626 auto layers = m_layerstack->editor(cmd.contextId());
627 auto layer = layers.getEditableLayer(cmd.layer());
628 if(layer.isNull()) {
629 qWarning("PutImage on non-existent layer #%d", cmd.layer());
630 return;
631 }
632
633 const int expectedLen = cmd.width() * cmd.height() * 4;
634 QByteArray data = qUncompress(cmd.image());
635 if(data.length() != expectedLen) {
636 qWarning() << "Invalid putImage: Expected" << expectedLen << "bytes, but got" << data.length();
637 return;
638 }
639 QImage img(reinterpret_cast<const uchar*>(data.constData()), cmd.width(), cmd.height(), QImage::Format_ARGB32_Premultiplied);
640 layer.putImage(cmd.x(), cmd.y(), img, paintcore::BlendMode::Mode(cmd.blendmode()));
641
642 if(_showallmarkers || cmd.contextId() != m_myId)
643 emit userMarkerMove(cmd.contextId(), layer->id(), QPoint(cmd.x() + cmd.width()/2, cmd.y()+cmd.height()/2));
644 }
645
handlePutTile(const protocol::PutTile & cmd)646 void StateTracker::handlePutTile(const protocol::PutTile &cmd)
647 {
648 auto layers = m_layerstack->editor(cmd.contextId());
649 auto layer = layers.getEditableLayer(cmd.layer());
650 if(layer.isNull()) {
651 qWarning("PutTile on non-existent layer #%d", cmd.layer());
652 return;
653 }
654
655 paintcore::Tile t;
656 if(cmd.isSolidColor()) {
657 t = paintcore::Tile(QColor::fromRgba(cmd.color()), cmd.contextId());
658
659 } else {
660 QByteArray data = qUncompress(cmd.image());
661 if(data.length() != paintcore::Tile::BYTES) {
662 qWarning() << "Invalid putTile: Expected" << paintcore::Tile::BYTES << "bytes, but got" << data.length();
663 return;
664 }
665
666 t = paintcore::Tile(data, cmd.contextId());
667 }
668
669 layer.putTile(cmd.column(), cmd.row(), cmd.repeat(), t, cmd.sublayer());
670 }
671
handleFillRect(const protocol::FillRect & cmd)672 void StateTracker::handleFillRect(const protocol::FillRect &cmd)
673 {
674 auto layers = m_layerstack->editor(cmd.contextId());
675 auto layer = layers.getEditableLayer(cmd.layer());
676 if(layer.isNull()) {
677 qWarning("FillRect on non-existent layer #%d", cmd.layer());
678 return;
679 }
680
681 layer.fillRect(QRect(cmd.x(), cmd.y(), cmd.width(), cmd.height()), QColor::fromRgba(cmd.color()), paintcore::BlendMode::Mode(cmd.blend()));
682
683 if(_showallmarkers || cmd.contextId() != m_myId)
684 emit userMarkerMove(cmd.contextId(), layer->id(), QPoint(cmd.x() + cmd.width()/2, cmd.y()+cmd.height()/2));
685 }
686
handleMoveRegion(const protocol::MoveRegion & cmd)687 void StateTracker::handleMoveRegion(const protocol::MoveRegion &cmd)
688 {
689 auto layers = m_layerstack->editor(cmd.contextId());
690 auto layer = layers.getEditableLayer(cmd.layer());
691 if(layer.isNull()) {
692 qWarning("MoveRegion on non-existent layer #%d", cmd.layer());
693 return;
694 }
695
696 if(cmd.contextId() == m_myId) {
697 // Moving the layer for real: make sure my preview is removed
698 layer.removeSublayer(-1);
699 }
700
701 // Source region bounding rectangle
702 const QRect bounds(cmd.bx(), cmd.by(), cmd.bw(), cmd.bh());
703
704 // Target quad
705 const QPolygon target({
706 QPoint(cmd.x1(), cmd.y1()),
707 QPoint(cmd.x2(), cmd.y2()),
708 QPoint(cmd.x3(), cmd.y3()),
709 QPoint(cmd.x4(), cmd.y4())
710 });
711
712 // Sanity check: without a size limit, a user could create huge temporary images and potentially other clients
713 const auto targetSize = target.boundingRect().size();
714 const int targetArea = targetSize.width() * targetSize.height();
715 if(targetArea > (m_layerstack->width()+1) * (m_layerstack->height()+1)) {
716 qWarning("moveRegion: cannot scale beyond image size");
717 return;
718 }
719
720 // Get mask bitmap
721 QImage mask;
722 if(!cmd.mask().isEmpty()) {
723 const int expectedLen = (cmd.bw()+31)/32 * 4 * cmd.bh(); // 1bpp lines padded to 32bit boundaries
724 QByteArray maskData = qUncompress(cmd.mask());
725 if(maskData.length() != expectedLen) {
726 qWarning("Invalid moveRegion mask: Expected %d bytes, but got %d", expectedLen, maskData.length());
727 return;
728 }
729
730 mask = QImage(reinterpret_cast<const uchar*>(maskData.constData()), cmd.bw(), cmd.bh(), QImage::Format_Mono);
731 mask.setColor(0, 0);
732 mask.setColor(1, 0xffffffff);
733 mask = mask.convertToFormat(QImage::Format_ARGB32_Premultiplied);
734 }
735
736 // Extract selected pixels
737 QImage selbuf = layer->toImage().copy(bounds); // TODO optimize
738
739 // Mask out unselected pixels (if necessary)
740 if(!mask.isNull()) {
741 QPainter mp(&selbuf);
742 mp.setCompositionMode(QPainter::CompositionMode_DestinationIn);
743 mp.drawImage(0, 0, mask);
744 }
745
746 // Transform selected pixels
747
748 QPoint offset;
749 QImage transformed;
750 if(target.boundingRect().size() == bounds.size() && target[0].x() < target[1].x()) {
751 // Just translation
752 transformed = selbuf;
753 offset = target[0];
754
755 } else {
756 transformed = tools::SelectionTool::transformSelectionImage(selbuf, target, &offset);
757 if(transformed.isNull()) {
758 qWarning("moveRegion: transformation failed (%d, %d -> %d, %d -> %d, %d -> %d, %d)!",
759 cmd.x1(), cmd.y1(), cmd.x2(), cmd.y2(), cmd.x3(), cmd.y3(), cmd.x4(), cmd.y4());
760 return;
761 }
762 }
763
764 // Erase selection mask and draw transformed image
765 if(mask.isNull()) {
766 layer.fillRect(bounds, Qt::transparent, paintcore::BlendMode::MODE_REPLACE);
767 } else {
768 layer.putImage(bounds.x(), bounds.y(), mask, paintcore::BlendMode::MODE_ERASE);
769 }
770
771 layer.putImage(offset.x(), offset.y(), transformed, paintcore::BlendMode::MODE_NORMAL);
772
773 if(_showallmarkers || cmd.contextId() != m_myId)
774 emit userMarkerMove(cmd.contextId(), layer->id(), target.boundingRect().center());
775 }
776
handleUndoPoint(const protocol::UndoPoint & cmd,bool replay,int pos)777 void StateTracker::handleUndoPoint(const protocol::UndoPoint &cmd, bool replay, int pos)
778 {
779 // New undo point. This branches the undo history. Since we store the
780 // commands in a linear sequence, this branching is represented by marking
781 // the unreachable commands as GONE.
782 if(!replay) {
783 int i = pos - 1; // skip the one just added
784 int upCount = 1;
785
786 // Mark undone actions as GONE
787 while(m_history.isValidIndex(i) && upCount < protocol::UNDO_DEPTH_LIMIT) {
788 protocol::MessagePtr msg = m_history.at(i);
789 if(msg->type() == protocol::MSG_UNDOPOINT)
790 ++upCount;
791 if(msg->contextId() == cmd.contextId()) {
792 // optimization: we can stop searching after finding the first GONE command
793 if(msg->type() != protocol::MSG_UNDO && msg->undoState() == protocol::GONE)
794 break;
795 else if(msg->undoState() == protocol::UNDONE)
796 msg->setUndoState(protocol::GONE);
797 }
798 --i;
799 }
800
801 // Keep rewinding until the oldest reachable undo point is found
802 while(m_history.isValidIndex(i) && upCount < protocol::UNDO_DEPTH_LIMIT) {
803 if(m_history.at(i)->type() == protocol::MSG_UNDOPOINT) {
804 ++upCount;
805 }
806 --i;
807 }
808
809 // Release all state savepoints older then the oldest UndoPoint
810 if(upCount>=protocol::UNDO_DEPTH_LIMIT) {
811 if(!m_localfork.isEmpty())
812 i = qMin(i, m_localfork.offset() - 1);
813
814 QMutableListIterator<StateSavepoint> spi(m_savepoints);
815 spi.toBack();
816
817 // In order to be able to return to the oldest undo point, we must leave
818 // one snapshot that is as old, or older.
819 bool first = true;
820
821 while(spi.hasPrevious()) {
822 const StateSavepoint &sp = spi.previous();
823 if(sp->streampointer <= i) {
824 if(first)
825 first = false;
826 else
827 spi.remove();
828 }
829 }
830 }
831 }
832
833 // Clear out history older than the oldest savepoint
834 Q_ASSERT(!m_savepoints.isEmpty());
835 m_history.cleanup(m_savepoints.first()->streampointer);
836
837 // Make a new savepoint (if possible)
838 makeSavepoint(pos);
839
840 // To be correct, we should set the "participated" flag on any command
841 // sent by us. In practice, however, an UndoPoint is always sent
842 // when making changes so it is enough to set the flag here.
843 if(cmd.contextId() == localId())
844 m_hasParticipated = true;
845 }
846
handleUndo(protocol::Undo & cmd)847 void StateTracker::handleUndo(protocol::Undo &cmd)
848 {
849 // Undo/redo commands are never replayed, so start
850 // by marking it as unavailable.
851 cmd.setUndoState(protocol::GONE);
852
853 const uint8_t ctxid = cmd.overrideId() ? cmd.overrideId() : cmd.contextId();
854
855 // Step 1. Find undo or redo point
856 int pos = m_history.end();
857 int upCount = 0;
858
859 if(cmd.isRedo()) {
860 // Find the oldest undone UndoPoint
861 int redostart = pos;
862 while(m_history.isValidIndex(--pos) && upCount <= protocol::UNDO_DEPTH_LIMIT) {
863 const protocol::MessagePtr msg = m_history.at(pos);
864 if(msg->type() == protocol::MSG_UNDOPOINT) {
865 ++upCount;
866 if(msg->contextId() == ctxid) {
867 if(msg->undoState() != protocol::DONE)
868 redostart = pos;
869 else
870 break;
871 }
872 }
873 }
874
875 if(redostart == m_history.end()) {
876 qDebug() << "nothing to redo for user" << cmd.contextId();
877 return;
878 }
879 pos = redostart;
880
881 } else {
882 // Find the newest UndoPoint not marked as undone.
883 while(m_history.isValidIndex(--pos) && upCount <= protocol::UNDO_DEPTH_LIMIT) {
884 const protocol::MessagePtr msg = m_history.at(pos);
885 if(msg->type() == protocol::MSG_UNDOPOINT) {
886 ++upCount;
887 if(msg->contextId() == ctxid && msg->undoState() == protocol::DONE)
888 break;
889 }
890 }
891 }
892
893 if(upCount > protocol::UNDO_DEPTH_LIMIT) {
894 qDebug() << "user" << cmd.contextId() << "cannot undo/redo beyond history limit";
895 return;
896 }
897
898 // pos is now at the starting UndoPoint
899 if(!m_history.isValidIndex(pos)) {
900 qWarning() << "Cannot " << (cmd.isRedo() ? "redo" : "undo") << "action by user" << ctxid << ": not enough messages in buffer!";
901 return;
902 }
903
904 // Step 2. Find nearest save point
905 StateSavepoint savepoint;
906 for(int i=m_savepoints.count()-1;i>=0;--i) {
907 if(m_savepoints.at(i)->streampointer <= pos) {
908 savepoint = m_savepoints.at(i);
909 break;
910 }
911 }
912
913 if(!savepoint) {
914 qWarning() << "Cannot" << (cmd.isRedo() ? "redo" : "undo") << "action by user" << ctxid << ": no savepoint found!";
915 return;
916 }
917
918 // Step 3. (Un)mark all actions by the user as undone
919 if(cmd.isRedo()) {
920 int i=pos;
921 int sequence=2;
922 // Un-undo messages until the start of the next undone sequence
923 while(i<m_history.end()) {
924 protocol::MessagePtr msg = m_history.at(i);
925 if(msg->contextId() == ctxid) {
926 if(msg->type() == protocol::MSG_UNDOPOINT && msg->undoState() != protocol::GONE)
927 if(--sequence==0)
928 break;
929
930 // GONE messages cannot be redone
931 if(msg->undoState() == protocol::UNDONE)
932 msg->setUndoState(protocol::DONE);
933 }
934 ++i;
935 }
936
937 } else {
938 // Mark all messages from undo point to the end as undone.
939 for(int i=pos;i<m_history.end();++i) {
940 protocol::MessagePtr msg = m_history.at(i);
941 if(msg->contextId() == ctxid)
942 msg->setUndoState(protocol::MessageUndoState(protocol::UNDONE | msg->undoState()));
943 }
944 }
945
946 // Step 4. Revert to the savepoint and replay with undone commands removed (or added back)
947 revertSavepointAndReplay(savepoint);
948 }
949
createSavepoint(int pos)950 StateSavepoint StateTracker::createSavepoint(int pos)
951 {
952 auto *data = new StateSavepoint::Data;
953 data->timestamp = QDateTime::currentMSecsSinceEpoch();
954 data->streampointer = pos;
955 data->canvas = m_layerstack->makeSavepoint();
956 data->layermodel = m_layerlist->getLayers();
957
958 return StateSavepoint(data);
959 }
960
makeSavepoint(int pos)961 void StateTracker::makeSavepoint(int pos)
962 {
963 // Don't make savepoints while a local fork exists, since
964 // there will be stuff on the canvas that is not yet in
965 // the mainline session history
966 if(!m_localfork.isEmpty())
967 return;
968
969 // Check if sufficient time and actions has elapsed from previous savepoint
970 if(!m_savepoints.isEmpty()) {
971 static const qint64 MIN_INTERVAL_MS = 1000;
972 static const int MIN_INTERVAL_MSGS = 100;
973
974 const StateSavepoint sp = m_savepoints.last();
975 const auto now = QDateTime::currentMSecsSinceEpoch();
976 if(now - sp->timestamp < MIN_INTERVAL_MS && m_history.end() - sp->streampointer < MIN_INTERVAL_MSGS)
977 return;
978 }
979
980 // Looks like a good spot for a savepoint
981 const auto sp = createSavepoint(pos);
982 m_savepoints << sp;
983
984 if(m_resetpoints.isEmpty() || (sp.timestamp() - m_resetpoints.last().timestamp()) > (10*1000)) {
985 while(m_resetpoints.size() >= 6)
986 m_resetpoints.removeFirst();
987 m_resetpoints << sp;
988 }
989 }
990
991
resetToSavepoint(const StateSavepoint savepoint)992 void StateTracker::resetToSavepoint(const StateSavepoint savepoint)
993 {
994 // This function is called when jumping to a recorded savepoint
995 if(!savepoint) {
996 qWarning("resetToSavepoint() was called with a null savepoint!");
997 return;
998 }
999
1000 m_history.resetTo(savepoint->streampointer);
1001 m_savepoints.clear();
1002
1003 m_layerstack->editor(0).restoreSavepoint(savepoint->canvas);
1004 m_layerlist->setLayers(savepoint->layermodel);
1005
1006 m_savepoints.append(savepoint);
1007 }
1008
revertSavepointAndReplay(const StateSavepoint savepoint)1009 void StateTracker::revertSavepointAndReplay(const StateSavepoint savepoint)
1010 {
1011 // This function is called when reverting to an earlier state to undo
1012 // an action.
1013 if(!savepoint) {
1014 qWarning("revertSavepointAndReplay() was called with a null savepoint!");
1015 return;
1016 }
1017 if(!m_savepoints.contains(savepoint)) {
1018 qWarning("revertSavepointAndReplay() the given savepoint was not found!");
1019 return;
1020 }
1021
1022 m_layerstack->editor(0).restoreSavepoint(savepoint->canvas);
1023 m_layerlist->setLayers(savepoint->layermodel);
1024
1025 // Reverting a savepoint destroys all newer savepoints
1026 while(m_savepoints.last() != savepoint)
1027 m_savepoints.removeLast();
1028
1029 // Replay all not-undo actions (and local fork)
1030 int pos = savepoint->streampointer + 1;
1031 while(pos < m_history.end()) {
1032 if(m_history.at(pos)->undoState() == protocol::DONE) {
1033 handleCommand(m_history.at(pos), true, pos);
1034 }
1035 ++pos;
1036 }
1037
1038 // Replay the local fork
1039 if(!m_localfork.isEmpty()) {
1040 Q_ASSERT(m_localfork.offset() >= savepoint->streampointer);
1041 m_localfork.setOffset(pos-1);
1042 const auto local = m_localfork.messages();
1043 for(const protocol::MessagePtr &msg : local) {
1044 if(msg->type() != protocol::MSG_UNDO && msg->type() != protocol::MSG_UNDOPOINT)
1045 handleCommand(msg, true, pos);
1046 }
1047 }
1048 }
1049
handleTruncateHistory()1050 void StateTracker::handleTruncateHistory()
1051 {
1052 int pos = m_history.end()-1;
1053 int upCount = 0;
1054
1055 qWarning("Truncating undo history at %d", pos);
1056 while(m_history.isValidIndex(pos) && upCount <= protocol::UNDO_DEPTH_LIMIT) {
1057 protocol::MessagePtr msg = m_history.at(pos);
1058
1059 if(msg->type() == protocol::MSG_UNDOPOINT) {
1060 ++upCount;
1061 msg->setUndoState(protocol::GONE);
1062 }
1063
1064 --pos;
1065 }
1066 qWarning("Marked %d UPs", upCount);
1067 }
1068
handleAnnotationCreate(const protocol::AnnotationCreate & cmd)1069 void StateTracker::handleAnnotationCreate(const protocol::AnnotationCreate &cmd)
1070 {
1071 m_layerstack->annotations()->addAnnotation(cmd.id(), QRect(cmd.x(), cmd.y(), cmd.w(), cmd.h()));
1072 if(cmd.contextId() == localId())
1073 emit myAnnotationCreated(cmd.id());
1074 }
1075
handleAnnotationReshape(const protocol::AnnotationReshape & cmd)1076 void StateTracker::handleAnnotationReshape(const protocol::AnnotationReshape &cmd)
1077 {
1078 m_layerstack->annotations()->reshapeAnnotation(cmd.id(), QRect(cmd.x(), cmd.y(), cmd.w(), cmd.h()));
1079 }
1080
handleAnnotationEdit(const protocol::AnnotationEdit & cmd)1081 void StateTracker::handleAnnotationEdit(const protocol::AnnotationEdit &cmd)
1082 {
1083 m_layerstack->annotations()->changeAnnotation(
1084 cmd.id(),
1085 cmd.text(),
1086 cmd.flags() & protocol::AnnotationEdit::FLAG_PROTECT,
1087 cmd.flags() & (protocol::AnnotationEdit::FLAG_VALIGN_BOTTOM|protocol::AnnotationEdit::FLAG_VALIGN_CENTER),
1088 QColor::fromRgba(cmd.bg())
1089 );
1090 }
1091
handleAnnotationDelete(const protocol::AnnotationDelete & cmd)1092 void StateTracker::handleAnnotationDelete(const protocol::AnnotationDelete &cmd)
1093 {
1094 m_layerstack->annotations()->deleteAnnotation(cmd.id());
1095 }
1096
1097 /**
1098 * @brief Get the affected area of the given message
1099 *
1100 * Note. This uses the current state!
1101 *
1102 * @param msg
1103 * @return
1104 */
affectedArea(protocol::MessagePtr msg) const1105 AffectedArea StateTracker::affectedArea(protocol::MessagePtr msg) const
1106 {
1107 Q_ASSERT(msg->isCommand());
1108
1109 switch(msg->type()) {
1110 using namespace protocol;
1111 case MSG_LAYER_CREATE:
1112 case MSG_LAYER_ATTR:
1113 case MSG_LAYER_RETITLE:
1114 return AffectedArea(AffectedArea::LAYERATTRS, msg->layer());
1115 case MSG_LAYER_VISIBILITY: return AffectedArea(AffectedArea::USERATTRS, 0);
1116
1117 case MSG_PUTIMAGE: {
1118 const PutImage &m = msg.cast<PutImage>();
1119 return AffectedArea(AffectedArea::PIXELS, m.layer(), QRect(m.x(), m.y(), m.width(), m.height()));
1120 }
1121 case MSG_PUTTILE: {
1122 const PutTile &m = msg.cast<PutTile>();
1123 return AffectedArea(AffectedArea::PIXELS, m.layer(), QRect(
1124 m.column() * paintcore::Tile::SIZE,
1125 m.row() * paintcore::Tile::SIZE,
1126 paintcore::Tile::SIZE, paintcore::Tile::SIZE));
1127 }
1128
1129 case MSG_DRAWDABS_CLASSIC:
1130 case MSG_DRAWDABS_PIXEL:
1131 case MSG_DRAWDABS_PIXEL_SQUARE: {
1132 const DrawDabs &dd = msg.cast<DrawDabs>();
1133
1134 // Indirect drawing mode: check bounds in PenUp
1135 if(dd.isIndirect())
1136 return AffectedArea(AffectedArea::USERATTRS, 0);
1137
1138 return AffectedArea(AffectedArea::PIXELS, dd.layer(), dd.bounds());
1139 }
1140 case MSG_PEN_UP: {
1141 QPair<int,QRect> bounds = m_layerstack->findChangeBounds(msg->contextId());
1142 if(bounds.first)
1143 return AffectedArea(AffectedArea::PIXELS, bounds.first, bounds.second);
1144 else
1145 return AffectedArea(AffectedArea::USERATTRS, 0);
1146 }
1147 case MSG_FILLRECT: {
1148 const FillRect &fr = msg.cast<FillRect>();
1149 return AffectedArea(AffectedArea::PIXELS, fr.layer(), QRect(fr.x(), fr.y(), fr.width(), fr.height()));
1150 }
1151
1152 case MSG_ANNOTATION_CREATE:
1153 case MSG_ANNOTATION_RESHAPE:
1154 case MSG_ANNOTATION_EDIT:
1155 case MSG_ANNOTATION_DELETE:
1156 return AffectedArea(AffectedArea::ANNOTATION, msg->layer());
1157
1158 case MSG_REGION_MOVE: {
1159 const MoveRegion &mr = msg.cast<MoveRegion>();
1160 return AffectedArea(AffectedArea::PIXELS, mr.layer(), mr.sourceBounds().united(mr.targetBounds()));
1161 }
1162
1163 case MSG_UNDOPOINT: return AffectedArea(AffectedArea::USERATTRS, 0);
1164
1165 case MSG_CANVAS_BACKGROUND: return AffectedArea(AffectedArea::PIXELS, -1, QRect(0, 0, 1, 1));
1166
1167 default:
1168 #ifndef NDEBUG
1169 qWarning("%s: affects EVERYTHING", qPrintable(msg->messageName()));
1170 #endif
1171 return AffectedArea(AffectedArea::EVERYTHING, 0);
1172 }
1173 }
1174
1175 }
1176