Pencil2D Animation
Download Community News Docs Contribute
  • Overview
  • Articles
  • Code
  •  
  • Class List
  • Class Index
  • Class Hierarchy
  • Class Members
  • File List
Loading...
Searching...
No Matches
  • core_lib
  • src
  • interface
editor.cpp
1/*
2
3Pencil2D - Traditional Animation Software
4Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5Copyright (C) 2012-2020 Matthew Chiawen Chang
6
7This program is free software; you can redistribute it and/or
8modify it under the terms of the GNU General Public License
9as published by the Free Software Foundation; version 2 of the License.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16*/
17
18#include "editor.h"
19
20#include <QTimer>
21#include <QImageReader>
22#include <QDropEvent>
23#include <QTemporaryDir>
24
25#include "object.h"
26#include "vectorimage.h"
27#include "bitmapimage.h"
28#include "soundclip.h"
29#include "camera.h"
30#include "layerbitmap.h"
31#include "layervector.h"
32#include "layercamera.h"
33#include "undoredocommand.h"
34
35#include "movetool.h"
36
37#include "colormanager.h"
38#include "filemanager.h"
39#include "toolmanager.h"
40#include "layermanager.h"
41#include "playbackmanager.h"
42#include "viewmanager.h"
43#include "preferencemanager.h"
44#include "soundmanager.h"
45#include "selectionmanager.h"
46#include "overlaymanager.h"
47#include "clipboardmanager.h"
48#include "undoredomanager.h"
49
50#include "scribblearea.h"
51
52Editor::Editor(QObject* parent) : QObject(parent)
53{
54}
55
56Editor::~Editor()
57{
58 // a lot more probably needs to be cleaned here...
59 clearTemporary();
60}
61
62bool Editor::init()
63{
64 // Initialize managers
65 mColorManager = new ColorManager(this);
66 mLayerManager = new LayerManager(this);
67 mToolManager = new ToolManager(this);
68 mPlaybackManager = new PlaybackManager(this);
69 mViewManager = new ViewManager(this);
70 mPreferenceManager = new PreferenceManager(this);
71 mSoundManager = new SoundManager(this);
72 mSelectionManager = new SelectionManager(this);
73 mOverlayManager = new OverlayManager(this);
74 mClipboardManager = new ClipboardManager(this);
75 mUndoRedoManager = new UndoRedoManager(this);
76
77 mAllManagers =
78 {
79 mColorManager,
80 mToolManager,
81 mLayerManager,
82 mPlaybackManager,
83 mViewManager,
84 mPreferenceManager,
85 mSoundManager,
86 mSelectionManager,
87 mOverlayManager,
88 mClipboardManager,
89 mUndoRedoManager
90 };
91
92 for (BaseManager* pManager : mAllManagers)
93 {
94 pManager->init();
95 }
96
97 makeConnections();
98
99 mIsAutosave = mPreferenceManager->isOn(SETTING::AUTO_SAVE);
100 mAutosaveNumber = mPreferenceManager->getInt(SETTING::AUTO_SAVE_NUMBER);
101
102 return true;
103}
104
105int Editor::currentFrame() const
106{
107 return mFrame;
108}
109
110int Editor::fps()
111{
112 return mPlaybackManager->fps();
113}
114
115void Editor::setFps(int fps)
116{
117 mPreferenceManager->set(SETTING::FPS, fps);
118 emit fpsChanged(fps);
119}
120
121void Editor::makeConnections()
122{
123 connect(mPreferenceManager, &PreferenceManager::optionChanged, this, &Editor::settingUpdated);
124 connect(mUndoRedoManager, &UndoRedoManager::didUpdateUndoStack, this, &Editor::updateAutoSaveCounter);
125 connect(mPreferenceManager, &PreferenceManager::optionChanged, mUndoRedoManager, &UndoRedoManager::onSettingChanged);
126
127 // XXX: This is a hack to prevent crashes until #864 is done (see #1412)
128 connect(mLayerManager, &LayerManager::layerDeleted, mUndoRedoManager, &UndoRedoManager::sanitizeLegacyBackupElementsAfterLayerDeletion);
129 connect(mLayerManager, &LayerManager::currentLayerWillChange, this, &Editor::onCurrentLayerWillChange);
130}
131
132void Editor::settingUpdated(SETTING setting)
133{
134 switch (setting)
135 {
136 case SETTING::AUTO_SAVE:
137 mIsAutosave = mPreferenceManager->isOn(SETTING::AUTO_SAVE);
138 break;
139 case SETTING::AUTO_SAVE_NUMBER:
140 mAutosaveNumber = mPreferenceManager->getInt(SETTING::AUTO_SAVE_NUMBER);
141 break;
142 case SETTING::ONION_TYPE:
143 mScribbleArea->onOnionSkinTypeChanged();
144 emit updateTimeLineCached();
145 break;
146 case SETTING::FRAME_POOL_SIZE:
147 mObject->setActiveFramePoolSize(mPreferenceManager->getInt(SETTING::FRAME_POOL_SIZE));
148 break;
149 case SETTING::LAYER_VISIBILITY:
150 mScribbleArea->setLayerVisibility(static_cast<LayerVisibility>(mPreferenceManager->getInt(SETTING::LAYER_VISIBILITY)));
151 emit updateTimeLine();
152 break;
153 default:
154 break;
155 }
156}
157
158void Editor::onCurrentLayerWillChange(int index)
159{
160 Q_UNUSED(index)
161
162 if (select() && select()->somethingSelected()) {
163 static_cast<MoveTool*>(tools()->getTool(MOVE))->applyTransformationAndDeselect();
164 }
165}
166
167void Editor::updateAutoSaveCounter()
168{
169 if (mIsAutosave == false)
170 return;
171
172 mAutosaveCounter++;
173 if (mAutosaveCounter >= mAutosaveNumber)
174 {
175 resetAutoSaveCounter();
176 emit needSave();
177 }
178}
179
180void Editor::resetAutoSaveCounter()
181{
182 mAutosaveCounter = 0;
183}
184
185void Editor::copy()
186{
187 Layer* currentLayer = layers()->currentLayer();
188
189 Q_ASSERT(currentLayer != nullptr);
190
191 if (!canCopy()) { return; }
192
193 backup(tr("Copy"));
194
195 if (currentLayer->hasAnySelectedFrames() && !select()->somethingSelected()) {
196 clipboards()->copySelectedFrames(currentLayer);
197 } else if (currentLayer->type() == Layer::BITMAP) {
198 BitmapImage* bitmapImage = static_cast<BitmapImage*>(currentLayer->getLastKeyFrameAtPosition(currentFrame()));
199 clipboards()->copyBitmapImage(bitmapImage, select()->mySelectionRect());
200 } else if (currentLayer->type() == Layer::VECTOR) {
201 VectorImage* vectorImage = static_cast<VectorImage*>(currentLayer->getLastKeyFrameAtPosition(currentFrame()));
202 clipboards()->copyVectorImage(vectorImage);
203 }
204}
205
206void Editor::copyAndCut()
207{
208 copy();
209
210 Layer* currentLayer = layers()->currentLayer();
211
212 if (currentLayer->hasAnySelectedFrames() && !select()->somethingSelected()) {
213 for (int pos : currentLayer->selectedKeyFramesPositions()) {
214 currentLayer->removeKeyFrame(pos);
215 }
216 emit layers()->currentLayerChanged(currentLayerIndex());
217 emit updateTimeLine();
218 return;
219 }
220
221 if (currentLayer->type() == Layer::BITMAP || currentLayer->type() == Layer::VECTOR) {
222 mScribbleArea->deleteSelection();
223 deselectAll();
224 }
225}
226
227void Editor::pasteFromPreviousFrame()
228{
229 Layer* currentLayer = layers()->currentLayer();
230 int prevFrame = currentLayer->getPreviousKeyFramePosition(mFrame);
231 if (!currentLayer->keyExists(mFrame) || prevFrame == mFrame)
232 {
233 return;
234 }
235
236 if (currentLayer->type() == Layer::BITMAP)
237 {
238 backup(tr("Paste from Previous Keyframe"));
239 BitmapImage* bitmapImage = static_cast<BitmapImage*>(currentLayer->getKeyFrameAt(prevFrame));
240 if (select()->somethingSelected())
241 {
242 BitmapImage copy = bitmapImage->copy(select()->mySelectionRect().toRect());
243 pasteToCanvas(&copy, mFrame);
244 }
245 else
246 {
247 pasteToCanvas(bitmapImage, mFrame);
248 }
249 }
250 else if (currentLayer->type() == Layer::VECTOR)
251 {
252 backup(tr("Paste from Previous Keyframe"));
253 VectorImage* vectorImage = static_cast<VectorImage*>(currentLayer->getKeyFrameAt(prevFrame));
254 pasteToCanvas(vectorImage, mFrame);
255 }
256}
257
258void Editor::pasteToCanvas(BitmapImage* bitmapImage, int frameNumber)
259{
260 Layer* currentLayer = layers()->currentLayer();
261
262 Q_ASSERT(currentLayer->type() == Layer::BITMAP);
263
264 if (select()->somethingSelected())
265 {
266 QRectF selection = select()->mySelectionRect();
267 if (bitmapImage->width() <= selection.width() && bitmapImage->height() <= selection.height())
268 {
269 bitmapImage->moveTopLeft(selection.topLeft());
270 }
271 else
272 {
273 bitmapImage->transform(selection, true);
274 }
275 }
276 mScribbleArea->handleDrawingOnEmptyFrame();
277 BitmapImage *canvasImage = static_cast<BitmapImage*>(currentLayer->getLastKeyFrameAtPosition(frameNumber));
278
279 // Paste clipboard onto current shown image
280 canvasImage->paste(bitmapImage);
281
282 // TODO: currently we don't support placing an image without already pasting it on an already existing
283 // image, this should be reworked such that a hovering selection could be shown, before applying it...
284 select()->setSelection(bitmapImage->bounds());
285 emit frameModified(frameNumber);
286}
287
288void Editor::pasteToCanvas(VectorImage* vectorImage, int frameNumber)
289{
290 Layer* currentLayer = layers()->currentLayer();
291
292 Q_ASSERT(currentLayer->type() == Layer::VECTOR);
293
294 deselectAll();
295 mScribbleArea->handleDrawingOnEmptyFrame();
296 VectorImage* canvasImage = static_cast<VectorImage*>(currentLayer->getLastKeyFrameAtPosition(frameNumber));
297 canvasImage->paste(*vectorImage);
298 select()->setSelection(vectorImage->getSelectionRect());
299 emit frameModified(frameNumber);
300}
301
302void Editor::pasteToFrames()
303{
304 auto clipboardFrames = clipboards()->getClipboardFrames();
305 Q_ASSERT(!clipboardFrames.empty());
306 Layer* currentLayer = layers()->currentLayer();
307
308 currentLayer->deselectAll();
309
310 int newPositionOffset = mFrame - clipboardFrames.cbegin()->first;
311 for (auto it = clipboardFrames.cbegin(); it != clipboardFrames.cend(); ++it)
312 {
313 int newPosition = it->first + newPositionOffset;
314
315 KeyFrame* keyFrameNewPos = currentLayer->getKeyFrameWhichCovers(newPosition);
316
317 if (keyFrameNewPos != nullptr) {
318
319 // Select and move any frames that may come into contact with the new position
320 currentLayer->newSelectionOfConnectedFrames(newPosition);
321 currentLayer->moveSelectedFrames(1);
322 }
323
324 KeyFrame* key = it->second;
325 // It's a bug if the keyframe is nullptr at this point...
326 Q_ASSERT(key != nullptr);
327
328 // TODO: undo/redo implementation
329 currentLayer->addKeyFrame(newPosition, key);
330 if (currentLayer->type() == Layer::SOUND)
331 {
332 auto soundClip = static_cast<SoundClip*>(key);
333 sound()->loadSound(soundClip, soundClip->fileName());
334 }
335
336 currentLayer->setFrameSelected(key->pos(), true);
337 }
338
339 layers()->notifyAnimationLengthChanged();
340}
341
342void Editor::paste()
343{
344 Layer* currentLayer = layers()->currentLayer();
345
346 Q_ASSERT(currentLayer != nullptr);
347
348 if (!canPaste()) { return; }
349
350 if (clipboards()->framesIsEmpty()) {
351
352 backup(tr("Paste"));
353
354 clipboards()->setFromSystemClipboard(mScribbleArea->getCentralPoint(), currentLayer);
355
356 BitmapImage clipboardImage = clipboards()->getBitmapClipboard();
357 VectorImage clipboardVectorImage = clipboards()->getVectorClipboard();
358 if (currentLayer->type() == Layer::BITMAP && clipboardImage.isLoaded()) {
359 pasteToCanvas(&clipboardImage, mFrame);
360 } else if (currentLayer->type() == Layer::VECTOR && !clipboardVectorImage.isEmpty()) {
361 pasteToCanvas(&clipboardVectorImage, mFrame);
362 }
363 } else {
364 // TODO: implement undo/redo
365 pasteToFrames();
366 }
367
368 emit frameModified(mFrame);
369}
370
371void Editor::flipSelection(bool flipVertical)
372{
373 if (flipVertical) {
374 backup(tr("Flip selection vertically"));
375 } else {
376 backup(tr("Flip selection horizontally"));
377 }
378 mScribbleArea->flipSelection(flipVertical);
379}
380
381void Editor::repositionImage(QPoint transform, int frame)
382{
383 if (layers()->currentLayer()->type() == Layer::BITMAP)
384 {
385 scrubTo(frame);
386 LayerBitmap* layer = static_cast<LayerBitmap*>(layers()->currentLayer());
387 QRect reposRect = layer->getFrameBounds(frame);
388 select()->setSelection(reposRect);
389 QPoint point = reposRect.topLeft();
390 point += transform;
391 layer->repositionFrame(point, frame);
392 backup(layer->id(), frame, tr("Reposition frame")); // TOOD: backup multiple reposition operations.
393 }
394}
395
396void Editor::setModified(int layerNumber, int frameNumber)
397{
398 Layer* layer = object()->getLayer(layerNumber);
399 if (layer == nullptr) { return; }
400
401 layer->setModified(frameNumber, true);
402 undoRedo()->rememberLastModifiedFrame(layerNumber, frameNumber);
403
404 emit frameModified(frameNumber);
405}
406
407void Editor::clipboardChanged()
408{
409 Layer* layer = layers()->currentLayer();
410
411 clipboards()->setFromSystemClipboard(mScribbleArea->getCentralPoint(), layer);
412
413 bool canCopyState = canCopy();
414 bool canPasteState = canPaste();
415
416 emit canCopyChanged(canCopyState);
417 emit canPasteChanged(canPasteState);
418}
419
420void Editor::setLayerVisibility(LayerVisibility visibility) {
421 mScribbleArea->setLayerVisibility(visibility);
422 emit updateTimeLine();
423}
424
425LayerVisibility Editor::layerVisibility()
426{
427 return mScribbleArea->getLayerVisibility();
428}
429
430qreal Editor::viewScaleInversed()
431{
432 return view()->getScaleInversed();
433}
434
435void Editor::increaseLayerVisibilityIndex()
436{
437 mScribbleArea->increaseLayerVisibilityIndex();
438 emit updateTimeLine();
439}
440
441void Editor::decreaseLayerVisibilityIndex()
442{
443 mScribbleArea->decreaseLayerVisibilityIndex();
444 emit updateTimeLine();
445}
446
447void Editor::addTemporaryDir(QTemporaryDir* const dir)
448{
449 mTemporaryDirs.append(dir);
450}
451
452void Editor::clearTemporary()
453{
454 while(!mTemporaryDirs.isEmpty())
455 {
456 QTemporaryDir* t = mTemporaryDirs.takeLast();
457 t->remove();
458 delete t;
459 }
460}
461
462Status Editor::openObject(const QString& strFilePath, const std::function<void(int)>& progressChanged, const std::function<void(int)>& progressRangeChanged)
463{
464 // Check for potential issues with the file
465 Q_ASSERT(!strFilePath.isEmpty());
466 QFileInfo fileInfo(strFilePath);
467 DebugDetails dd;
468 dd << QString("Raw file path: %1").arg(strFilePath);
469 dd << QString("Resolved file path: %1").arg(fileInfo.absoluteFilePath());
470 if (fileInfo.isDir())
471 {
472 return Status(Status::ERROR_FILE_CANNOT_OPEN,
473 dd,
474 tr("Could not open file"),
475 tr("The file you have selected is a directory, so we are unable to open it. "
476 "If you are are trying to open a project that uses the old structure, "
477 "please open the file ending with .pcl, not the data folder."));
478 }
479 if (!fileInfo.exists())
480 {
481 return Status(Status::FILE_NOT_FOUND,
482 dd,
483 tr("Could not open file"),
484 tr("The file you have selected does not exist, so we are unable to open it. "
485 "Please make sure that you've entered the correct path and that the file is accessible and try again."));
486 }
487 if (!fileInfo.isReadable())
488 {
489 dd << QString("Permissions: 0x%1").arg(static_cast<uint>(fileInfo.permissions()), 0, 16);
490 return Status(Status::ERROR_FILE_CANNOT_OPEN,
491 dd,
492 tr("Could not open file"),
493 tr("This program does not have permission to read the file you have selected. "
494 "Please check that you have read permissions for this file and try again."));
495 }
496
497 int progress = 0;
498 FileManager fm(this);
499 connect(&fm, &FileManager::progressChanged, [&progress, &progressChanged](int p)
500 {
501 progressChanged(progress = p);
502 });
503 connect(&fm, &FileManager::progressRangeChanged, [&progressRangeChanged](int max)
504 {
505 progressRangeChanged(max + 3);
506 });
507
508 QString fullPath = fileInfo.absoluteFilePath();
509
510 Object* object = fm.load(fullPath);
511
512 Status fmStatus = fm.error();
513 if (!fmStatus.ok())
514 {
515 dd.collect(fmStatus.details());
516 fmStatus.setDetails(dd);
517 return fmStatus;
518 }
519
520 if (object == nullptr)
521 {
522 return Status(Status::ERROR_FILE_CANNOT_OPEN,
523 dd,
524 tr("Could not open file"),
525 tr("An unknown error occurred while trying to load the file and we are not able to load your file."));
526 }
527
528 setObject(object);
529
530 progressChanged(progress + 1);
531
532 layers()->notifyAnimationLengthChanged();
533 setFps(playback()->fps());
534
535 return Status::OK;
536}
537
538Status Editor::setObject(Object* newObject)
539{
540 Q_ASSERT(newObject);
541
542 if (newObject == mObject.get())
543 {
544 return Status::SAFE;
545 }
546
547 mObject.reset(newObject);
548
549 updateObject();
550
551 // Make sure that object is fully loaded before calling managers.
552 for (BaseManager* m : mAllManagers)
553 {
554 m->load(mObject.get());
555 }
556 emit objectLoaded();
557
558 return Status::OK;
559}
560
561void Editor::updateObject()
562{
563 setCurrentLayerIndex(mObject->data()->getCurrentLayer());
564 scrubTo(mObject->data()->getCurrentFrame());
565
566 mAutosaveCounter = 0;
567 mAutosaveNeverAskAgain = false;
568
569 if (mPreferenceManager)
570 {
571 mObject->setActiveFramePoolSize(mPreferenceManager->getInt(SETTING::FRAME_POOL_SIZE));
572 }
573
574 emit updateLayerCount();
575}
576
577Status Editor::importBitmapImage(const QString& filePath, const QTransform& importTransform)
578{
579 QImageReader reader(filePath);
580
581 Q_ASSERT(layers()->currentLayer()->type() == Layer::BITMAP);
582 const auto layer = static_cast<LayerBitmap*>(layers()->currentLayer());
583
584 if (!layer->visible())
585 {
586 mScribbleArea->showLayerNotVisibleWarning();
587 return Status::SAFE;
588 }
589
590 Status status = Status::OK;
591 DebugDetails dd;
592 dd << QString("Raw file path: %1").arg(filePath);
593
594 QImage img(reader.size(), QImage::Format_ARGB32_Premultiplied);
595 if (!reader.read(&img)) {
596 QString format = reader.format();
597 if (!format.isEmpty())
598 {
599 dd << QString("QImageReader format: %1").arg(format);
600 }
601 dd << QString("QImageReader ImageReaderError type: %1").arg(reader.errorString());
602
603 QString errorDesc;
604 switch(reader.error())
605 {
606 case QImageReader::ImageReaderError::FileNotFoundError:
607 errorDesc = tr("File not found at path \"%1\". Please check the image is present at the specified location and try again.").arg(filePath);
608 break;
609 case QImageReader::UnsupportedFormatError:
610 errorDesc = tr("Image format is not supported. Please convert the image file to one of the following formats and try again:\n%1")
611 .arg(QString::fromUtf8(reader.supportedImageFormats().join(", ")));
612 break;
613 default:
614 errorDesc = tr("An error has occurred while reading the image. Please check that the file is a valid image and try again.");
615 }
616
617 status = Status(Status::FAIL, dd, tr("Import failed"), errorDesc);
618 }
619
620 const QPoint pos = importTransform.map(QPoint(-img.width() / 2,
621 -img.height() / 2));
622
623 if (!layer->keyExists(mFrame))
624 {
625 const bool ok = addNewKey();
626 Q_ASSERT(ok);
627 }
628 BitmapImage* bitmapImage = layer->getBitmapImageAtFrame(mFrame);
629 BitmapImage importedBitmapImage(pos, img);
630 bitmapImage->paste(&importedBitmapImage);
631 emit frameModified(bitmapImage->pos());
632
633 scrubTo(mFrame+1);
634
635 backup(tr("Import Image"));
636
637 return status;
638}
639
640Status Editor::importVectorImage(const QString& filePath)
641{
642 Q_ASSERT(layers()->currentLayer()->type() == Layer::VECTOR);
643
644 auto layer = static_cast<LayerVector*>(layers()->currentLayer());
645
646 Status status = Status::OK;
647 DebugDetails dd;
648 dd << QString("Raw file path: %1").arg(filePath);
649
650 VectorImage* vectorImage = layer->getVectorImageAtFrame(currentFrame());
651 if (vectorImage == nullptr)
652 {
653 addNewKey();
654 vectorImage = layer->getVectorImageAtFrame(currentFrame());
655 }
656
657 VectorImage importedVectorImage;
658 bool ok = importedVectorImage.read(filePath);
659 if (ok)
660 {
661 importedVectorImage.selectAll();
662 vectorImage->paste(importedVectorImage);
663 emit frameModified(importedVectorImage.pos());
664
665 backup(tr("Import Image"));
666 }
667 else {
668 status = Status(Status::FAIL, dd, tr("Import failed"), tr("You cannot import images into a vector layer."));
669 }
670
671 return status;
672}
673
674Status Editor::importImage(const QString& filePath, const ImportImageConfig importConfig)
675{
676 Layer* layer = layers()->currentLayer();
677
678 DebugDetails dd;
679 dd << QString("Raw file path: %1").arg(filePath);
680
681 QTransform transform;
682 switch (importConfig.positionType)
683 {
684 case ImportImageConfig::CenterOfCamera: {
685 LayerCamera* layerCam = static_cast<LayerCamera*>(layers()->getCameraLayerBelow(currentLayerIndex()));
686 Q_ASSERT(layerCam);
687 transform = layerCam->getViewAtFrame(importConfig.importFrame).inverted();
688 break;
689 }
690 case ImportImageConfig::CenterOfCameraFollowed: {
691 LayerCamera* camera = static_cast<LayerCamera*>(layers()->getCameraLayerBelow(currentLayerIndex()));
692 Q_ASSERT(camera);
693 transform = camera->getViewAtFrame(currentFrame()).inverted();
694 break;
695 }
696 case ImportImageConfig::CenterOfView: {
697 QPointF centralPoint = mScribbleArea->getCentralPoint();
698 transform = QTransform::fromTranslate(centralPoint.x(), centralPoint.y());
699 break;
700 }
701 case ImportImageConfig::CenterOfCanvas:
702 case ImportImageConfig::None: {
703 transform = QTransform();
704 break;
705 }
706 }
707
708 switch (layer->type())
709 {
710 case Layer::BITMAP:
711 return importBitmapImage(filePath, transform);
712
713 case Layer::VECTOR:
714 return importVectorImage(filePath);
715
716 default:
717 dd << QString("Current layer: %1").arg(layer->type());
718 return Status(Status::ERROR_INVALID_LAYER_TYPE, dd, tr("Import failed"), tr("You can only import images to a bitmap layer."));
719 }
720}
721
722Status Editor::importAnimatedImage(const QString& filePath, int frameSpacing, const std::function<void(int)>& progressChanged, const std::function<bool()>& wasCanceled)
723{
724 frameSpacing = qMax(1, frameSpacing);
725
726 DebugDetails dd;
727 dd << QString("Raw file path: %1").arg(filePath);
728
729 Layer* layer = layers()->currentLayer();
730 if (layer->type() != Layer::BITMAP)
731 {
732 dd << QString("Current layer: %1").arg(layer->type());
733 return Status(Status::ERROR_INVALID_LAYER_TYPE, dd, tr("Import failed"), tr("You can only import images to a bitmap layer."));
734 }
735 LayerBitmap* bitmapLayer = static_cast<LayerBitmap*>(layers()->currentLayer());
736
737 QImageReader reader(filePath);
738 dd << QString("QImageReader format: %1").arg(QString(reader.format()));
739 if (!reader.supportsAnimation()) {
740 return Status(Status::ERROR_INVALID_LAYER_TYPE, dd, tr("Import failed"), tr("The selected image has a format that does not support animation."));
741 }
742
743 QImage img(reader.size(), QImage::Format_ARGB32_Premultiplied);
744 const QPoint pos(view()->getView().dx() - (img.width() / 2),
745 view()->getView().dy() - (img.height() / 2));
746 int totalFrames = reader.imageCount();
747 while (reader.read(&img))
748 {
749 if (reader.error())
750 {
751 dd << QString("QImageReader ImageReaderError type: %1").arg(reader.errorString());
752
753 QString errorDesc;
754 switch(reader.error())
755 {
756 case QImageReader::ImageReaderError::FileNotFoundError:
757 errorDesc = tr("File not found at path \"%1\". Please check the image is present at the specified location and try again.").arg(filePath);
758 break;
759 case QImageReader::UnsupportedFormatError:
760 errorDesc = tr("Image format is not supported. Please convert the image file to one of the following formats and try again:\n%1")
761 .arg((QString)reader.supportedImageFormats().join(", "));
762 break;
763 default:
764 errorDesc = tr("An error has occurred while reading the image. Please check that the file is a valid image and try again.");
765 }
766
767 return Status(Status::FAIL, dd, tr("Import failed"), errorDesc);
768 }
769
770 if (!bitmapLayer->keyExists(mFrame))
771 {
772 addNewKey();
773 }
774 BitmapImage* bitmapImage = bitmapLayer->getBitmapImageAtFrame(mFrame);
775 BitmapImage importedBitmapImage(pos, img);
776 bitmapImage->paste(&importedBitmapImage);
777 emit frameModified(bitmapImage->pos());
778
779 if (wasCanceled())
780 {
781 break;
782 }
783
784 scrubTo(mFrame + frameSpacing);
785
786 backup(tr("Import Image"));
787
788 progressChanged(qFloor(qMin(static_cast<double>(reader.currentImageNumber()) / totalFrames, 1.0) * 100));
789 }
790
791 return Status::OK;
792}
793
794void Editor::selectAll() const
795{
796 Layer* layer = layers()->currentLayer();
797
798 QRectF rect;
799 if (layer->type() == Layer::BITMAP)
800 {
801 // Selects the drawn area (bigger or smaller than the screen). It may be more accurate to select all this way
802 // as the drawing area is not limited
803 BitmapImage *bitmapImage = static_cast<BitmapImage*>(layer->getLastKeyFrameAtPosition(mFrame));
804 if (bitmapImage == nullptr) { return; }
805
806 rect = bitmapImage->bounds();
807 }
808 else if (layer->type() == Layer::VECTOR)
809 {
810 VectorImage *vectorImage = static_cast<VectorImage*>(layer->getLastKeyFrameAtPosition(mFrame));
811 if (vectorImage != nullptr)
812 {
813 vectorImage->selectAll();
814 rect = vectorImage->getSelectionRect();
815 }
816 }
817 select()->setSelection(rect, false);
818}
819
820void Editor::deselectAll() const
821{
822 select()->resetSelectionProperties();
823
824 Layer* layer = layers()->currentLayer();
825 if (layer == nullptr) { return; }
826
827 if (layer->type() == Layer::VECTOR)
828 {
829 VectorImage *vectorImage = static_cast<VectorImage*>(layer->getLastKeyFrameAtPosition(mFrame));
830 if (vectorImage != nullptr)
831 {
832 vectorImage->deselectAll();
833 }
834 }
835
836 if (layer->hasAnySelectedFrames()) {
837 layer->deselectAll();
838 emit updateTimeLine();
839 }
840}
841
842void Editor::updateFrame()
843{
844 mScribbleArea->updateFrame();
845}
846
847void Editor::setCurrentLayerIndex(int i)
848{
849 mCurrentLayerIndex = i;
850
851 Layer* layer = mObject->getLayer(i);
852 for (auto mgr : mAllManagers)
853 {
854 mgr->workingLayerChanged(layer);
855 }
856}
857
858void Editor::scrubTo(int frame)
859{
860 if (select() && select()->somethingSelected()) {
861 static_cast<MoveTool*>(tools()->getTool(MOVE))->applyTransformationAndDeselect();
862 }
863
864 if (frame < 1) { frame = 1; }
865 mFrame = frame;
866
867 // FIXME: should not emit Timeline update here.
868 // Editor must be an individual class.
869 // Will remove all Timeline related code in Editor class.
870 if (mPlaybackManager && !mPlaybackManager->isPlaying())
871 {
872 emit updateTimeLineCached(); // needs to update the timeline to update onion skin positions
873 }
874 mObject->updateActiveFrames(frame);
875 emit scrubbed(frame);
876}
877
878void Editor::scrubForward()
879{
880 int nextFrame = mFrame + 1;
881 if (!playback()->isPlaying()) {
882 playback()->playScrub(nextFrame);
883 }
884 scrubTo(nextFrame);
885}
886
887void Editor::scrubBackward()
888{
889 if (currentFrame() > 1)
890 {
891 int previousFrame = mFrame - 1;
892 if (!playback()->isPlaying()) {
893 playback()->playScrub(previousFrame);
894 }
895 scrubTo(previousFrame);
896 }
897}
898
899KeyFrame* Editor::addNewKey()
900{
901 return addKeyFrame(layers()->currentLayerIndex(), currentFrame());
902}
903
904KeyFrame* Editor::addKeyFrame(const int layerNumber, int frameIndex)
905{
906 Layer* layer = mObject->getLayer(layerNumber);
907 Q_ASSERT(layer);
908
909 if (!layer->visible())
910 {
911 mScribbleArea->showLayerNotVisibleWarning();
912 return nullptr;
913 }
914
915 // Find next available space for a keyframe (where either no key exists or there is an empty sound key)
916 while (layer->keyExists(frameIndex))
917 {
918 if (layer->type() == Layer::SOUND
919 && layer->getKeyFrameAt(frameIndex)->fileName().isEmpty()
920 && layer->removeKeyFrame(frameIndex))
921 {
922 break;
923 }
924 else
925 {
926 frameIndex += 1;
927 }
928 }
929
930 const bool ok = layer->addNewKeyFrameAt(frameIndex);
931 Q_ASSERT(ok); // We already ensured that there is no keyframe at frameIndex, so this should always succeed
932 scrubTo(frameIndex); // currentFrameChanged() emit inside.
933
934 SAVESTATE_ID saveStateId = undoRedo()->createState(UndoRedoRecordType::KEYFRAME_ADD);
935 emit frameModified(frameIndex);
936 layers()->notifyAnimationLengthChanged();
937 KeyFrame* newFrame = layer->getKeyFrameAt(frameIndex);
938
939 undoRedo()->record(saveStateId, tr("Add frame"));
940
941 return newFrame;
942}
943
944void Editor::removeKey()
945{
946 Layer* layer = layers()->currentLayer();
947 Q_ASSERT(layer != nullptr);
948
949 if (!layer->visible())
950 {
951 mScribbleArea->showLayerNotVisibleWarning();
952 return;
953 }
954
955 if (!layer->keyExistsWhichCovers(currentFrame()))
956 {
957 scrubBackward();
958 return;
959 }
960
961 SAVESTATE_ID saveStateId = undoRedo()->createState(UndoRedoRecordType::KEYFRAME_REMOVE);
962 backup(tr("Remove frame"));
963
964 deselectAll();
965 layer->removeKeyFrame(currentFrame());
966 layers()->notifyAnimationLengthChanged();
967 emit layers()->currentLayerChanged(layers()->currentLayerIndex()); // trigger timeline repaint.
968
969 undoRedo()->record(saveStateId, tr("Remove frame"));
970}
971
972void Editor::scrubNextKeyFrame()
973{
974 Layer* currentLayer = layers()->currentLayer();
975 Q_ASSERT(currentLayer);
976
977 int nextPosition = currentLayer->getNextKeyFramePosition(currentFrame());
978 if (currentFrame() >= currentLayer->getMaxKeyFramePosition()) nextPosition = currentFrame() + 1;
979 scrubTo(nextPosition);
980}
981
982void Editor::scrubPreviousKeyFrame()
983{
984 Layer* layer = mObject->getLayer(layers()->currentLayerIndex());
985 Q_ASSERT(layer);
986
987 int prevPosition = layer->getPreviousKeyFramePosition(currentFrame());
988 scrubTo(prevPosition);
989}
990
991void Editor::switchVisibilityOfLayer(int layerNumber)
992{
993 Layer* layer = mObject->getLayer(layerNumber);
994 if (layer != nullptr) layer->switchVisibility();
995 mScribbleArea->onLayerChanged();
996
997 emit updateTimeLine();
998}
999
1000void Editor::swapLayers(int i, int j)
1001{
1002 bool didSwapLayer = mObject->swapLayers(i, j);
1003 if (!didSwapLayer) { return; }
1004
1005 if (j < i)
1006 {
1007 layers()->setCurrentLayer(j + 1);
1008 }
1009 else
1010 {
1011 layers()->setCurrentLayer(j - 1);
1012 }
1013 emit updateTimeLine();
1014 mScribbleArea->onLayerChanged();
1015}
1016
1017bool Editor::canSwapLayers(int layerIndexLeft, int layerIndexRight) const
1018{
1019 return mObject->canSwapLayers(layerIndexLeft, layerIndexRight);
1020}
1021
1022void Editor::prepareSave()
1023{
1024 for (auto mgr : mAllManagers)
1025 {
1026 mgr->save(mObject.get());
1027 }
1028}
1029
1030void Editor::clearCurrentFrame()
1031{
1032 mScribbleArea->clearImage();
1033}
1034
1035bool Editor::canCopy() const
1036{
1037 Layer* layer = layers()->currentLayer();
1038 KeyFrame* keyframe = layer->getLastKeyFrameAtPosition(mFrame);
1039
1040 switch (layer->type())
1041 {
1042 case Layer::SOUND:
1043 case Layer::CAMERA:
1044 return canCopyFrames(layer);
1045 case Layer::BITMAP:
1046 return canCopyBitmapImage(static_cast<BitmapImage*>(keyframe)) || canCopyFrames(layer);
1047 case Layer::VECTOR:
1048 return canCopyVectorImage(static_cast<VectorImage*>(keyframe)) || canCopyFrames(layer);
1049 default:
1050 Q_UNREACHABLE();
1051 }
1052}
1053
1054bool Editor::canPaste() const
1055{
1056 Layer* layer = layers()->currentLayer();
1057 auto clipboardMan = clipboards();
1058 auto layerType = layer->type();
1059
1060 return (layerType == clipboardMan->framesLayerType() && !clipboardMan->framesIsEmpty()) ||
1061 (layerType == Layer::BITMAP && clipboardMan->getBitmapClipboard().isLoaded()) ||
1062 (layerType == Layer::VECTOR && !clipboardMan->getVectorClipboard().isEmpty());
1063}
1064
1065bool Editor::canCopyFrames(const Layer* layer) const
1066{
1067 Q_ASSERT(layer != nullptr);
1068 return layer->hasAnySelectedFrames();
1069}
1070
1071bool Editor::canCopyBitmapImage(BitmapImage* bitmapImage) const
1072{
1073 return bitmapImage != nullptr && bitmapImage->isLoaded() && !bitmapImage->bounds().isEmpty();
1074}
1075
1076bool Editor::canCopyVectorImage(const VectorImage* vectorImage) const
1077{
1078 return vectorImage != nullptr && !vectorImage->isEmpty();
1079}
1080
1081void Editor::backup(const QString &undoText)
1082{
1083 undoRedo()->legacyBackup(undoText);
1084 updateAutoSaveCounter();
1085}
1086
1087bool Editor::backup(int layerNumber, int frameNumber, const QString &undoText)
1088{
1089 bool didBackup = undoRedo()->legacyBackup(layerNumber, frameNumber, undoText);
1090
1091 updateAutoSaveCounter();
1092 return didBackup;
1093}
BaseManager
Definition: basemanager.h:29
BitmapImage
Definition: bitmapimage.h:28
ClipboardManager
Definition: clipboardmanager.h:31
ClipboardManager::getClipboardFrames
std::map< int, KeyFrame * > getClipboardFrames()
Return a copy of all clipboard frames keyed by their position.
Definition: clipboardmanager.cpp:100
ClipboardManager::copySelectedFrames
void copySelectedFrames(const Layer *currentLayer)
Copy selected keyframes of any given layer and remember its type.
Definition: clipboardmanager.cpp:82
ClipboardManager::copyVectorImage
void copyVectorImage(const VectorImage *vectorImage)
Copy the entire vector image to clipboard, this operation does not yet support partial selections.
Definition: clipboardmanager.cpp:73
ClipboardManager::copyBitmapImage
void copyBitmapImage(BitmapImage *image, QRectF selectionRect)
Copy bitmap image to clipboard and save its latest position Additionally only a part of the image wil...
Definition: clipboardmanager.cpp:56
ColorManager
Definition: colormanager.h:27
DebugDetails
Definition: pencilerror.h:25
Editor::addNewKey
KeyFrame * addNewKey()
Attempts to create a new keyframe at the current frame and layer.
Definition: editor.cpp:899
Editor::frameModified
void frameModified(int frameNumber)
This should be emitted after modifying the frame content.
Editor::updateFrame
void updateFrame()
Will call update() and update the canvas Only call this directly If you need the cache to be intact a...
Definition: editor.cpp:842
Editor::scrubbed
void scrubbed(int frameNumber)
This should be emitted after scrubbing.
Editor::setLayerVisibility
void setLayerVisibility(LayerVisibility visibility)
The visibility value should match any of the VISIBILITY enum values.
Definition: editor.cpp:420
Editor::addKeyFrame
KeyFrame * addKeyFrame(int layerNumber, int frameIndex)
Attempts to create a new keyframe at the given position and layer.
Definition: editor.cpp:904
FileManager
Definition: filemanager.h:37
KeyFrame
Definition: keyframe.h:30
LayerBitmap
Definition: layerbitmap.h:26
LayerCamera
Definition: layercamera.h:30
Layer
Definition: layer.h:33
Layer::newSelectionOfConnectedFrames
bool newSelectionOfConnectedFrames(int position)
Make a selection from specified position until a blank spot appears The search is only looking forwar...
Definition: layer.cpp:453
Layer::addNewKeyFrameAt
bool addNewKeyFrameAt(int position)
Creates a new keyframe at the given position, unless one already exists.
Definition: layer.cpp:170
Layer::selectedKeyFramesPositions
QList< int > selectedKeyFramesPositions() const
Get selected keyframe positions sorted by position.
Definition: layer.h:62
Layer::addKeyFrame
virtual bool addKeyFrame(int position, KeyFrame *pKeyFrame)
Adds a keyframe at the given position, unless one already exists.
Definition: layer.cpp:191
LayerManager
Definition: layermanager.h:31
LayerManager::notifyAnimationLengthChanged
void notifyAnimationLengthChanged()
This should be emitted whenever the animation length frames, eg.
Definition: layermanager.cpp:403
LayerVector
Definition: layervector.h:26
MoveTool
Definition: movetool.h:31
Object
Definition: object.h:42
OverlayManager
Definition: overlaymanager.h:32
PlaybackManager
Definition: playbackmanager.h:30
PreferenceManager
Definition: preferencemanager.h:28
ScribbleArea::updateFrame
void updateFrame()
Update frame.
Definition: scribblearea.cpp:200
ScribbleArea::handleDrawingOnEmptyFrame
void handleDrawingOnEmptyFrame()
Call this when starting to use a paint tool.
Definition: scribblearea.cpp:824
ScribbleArea::onOnionSkinTypeChanged
void onOnionSkinTypeChanged()
Onion skin type changed, all frames will be affected.
Definition: scribblearea.cpp:358
ScribbleArea::onLayerChanged
void onLayerChanged()
Layer changed, invalidate relevant cache.
Definition: scribblearea.cpp:346
SelectionManager
The SelectionManager class acts as the "Brain" of the selection system.
Definition: selectionmanager.h:51
SelectionManager::setSelection
void setSelection(QRectF rect, bool roundPixels=false)
Defines the selection area.
Definition: selectionmanager.cpp:273
SoundClip
Definition: soundclip.h:27
SoundManager
Definition: soundmanager.h:30
Status
Definition: pencilerror.h:40
ToolManager
Definition: toolmanager.h:32
UndoRedoManager
Definition: undoredomanager.h:119
UndoRedoManager::sanitizeLegacyBackupElementsAfterLayerDeletion
void sanitizeLegacyBackupElementsAfterLayerDeletion(int layerIndex)
Restores integrity of the backup elements after a layer has been deleted.
Definition: undoredomanager.cpp:570
UndoRedoManager::record
void record(SAVESTATE_ID SaveStateId, const QString &description)
Records the given save state.
Definition: undoredomanager.cpp:96
UndoRedoManager::createState
SAVESTATE_ID createState(UndoRedoRecordType recordType)
Prepares and returns an save state with common data.
Definition: undoredomanager.cpp:261
VectorImage
Definition: vectorimage.h:32
VectorImage::deselectAll
void deselectAll()
VectorImage::deselectAll.
Definition: vectorimage.cpp:839
VectorImage::paste
void paste(VectorImage &)
VectorImage::paste.
Definition: vectorimage.cpp:1049
VectorImage::read
bool read(QString filePath)
VectorImage::read.
Definition: vectorimage.cpp:73
VectorImage::selectAll
void selectAll()
VectorImage::selectAll.
Definition: vectorimage.cpp:813
ViewManager
Definition: viewmanager.h:26
ViewManager::getScaleInversed
qreal getScaleInversed() const
Definition: viewmanager.cpp:111
QFileInfo
QImage
QImage::Format_ARGB32_Premultiplied
Format_ARGB32_Premultiplied
QImageReader
QImageReader::UnsupportedFormatError
UnsupportedFormatError
QList::append
void append(const T &value)
QList::isEmpty
bool isEmpty() const const
QList::takeLast
T takeLast()
QObject
QObject::connect
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject::tr
QString tr(const char *sourceText, const char *disambiguation, int n)
QPoint
QPointF
QPointF::x
qreal x() const const
QPointF::y
qreal y() const const
QRect
QRect::isEmpty
bool isEmpty() const const
QRect::topLeft
QPoint topLeft() const const
QRectF
QRectF::height
qreal height() const const
QRectF::toRect
QRect toRect() const const
QRectF::topLeft
QPointF topLeft() const const
QRectF::width
qreal width() const const
QString
QString::arg
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString::fromUtf8
QString fromUtf8(const char *str, int size)
QString::isEmpty
bool isEmpty() const const
QTemporaryDir
QTemporaryDir::remove
bool remove()
QTransform
QTransform::fromTranslate
QTransform fromTranslate(qreal dx, qreal dy)
QTransform::inverted
QTransform inverted(bool *invertible) const const
QTransform::map
QPoint map(const QPoint &point) const const
ImportImageConfig
Definition: importimageconfig.h:22
Generated on Mon May 4 2026 07:50:47 for Pencil2D by doxygen 1.9.6 based on revision 3ed50cdd696e72315cedf30508d3572536c3876e