17#include "actioncommands.h"
19#include <QInputDialog>
21#include <QProgressDialog>
22#include <QApplication>
23#include <QDesktopServices>
24#include <QStandardPaths>
30#include "viewmanager.h"
31#include "layermanager.h"
32#include "scribblearea.h"
33#include "toolmanager.h"
34#include "soundmanager.h"
35#include "playbackmanager.h"
36#include "colormanager.h"
37#include "preferencemanager.h"
38#include "selectionmanager.h"
42#include "layercamera.h"
43#include "layersound.h"
44#include "layerbitmap.h"
45#include "layervector.h"
46#include "bitmapimage.h"
47#include "vectorimage.h"
51#include "importimageseqdialog.h"
52#include "importpositiondialog.h"
53#include "movieimporter.h"
54#include "movieexporter.h"
55#include "filedialog.h"
56#include "exportmoviedialog.h"
57#include "exportimagedialog.h"
58#include "aboutdialog.h"
59#include "doubleprogressdialog.h"
60#include "checkupdatesdialog.h"
61#include "errordialog.h"
69ActionCommands::~ActionCommands() {}
71Status ActionCommands::importAnimatedImage()
77 return Status::CANCELED;
79 int frameSpacing = fileDialog.getSpace();
80 QString strImgFileLower = fileDialog.getFilePath();
83 positionDialog.exec();
86 return Status::CANCELED;
90 QProgressDialog progressDialog(
tr(
"Importing Animated Image..."),
tr(
"Abort"), 0, 100, mParent);
91 hideQuestionMark(progressDialog);
93 progressDialog.show();
95 Status st = mEditor->importAnimatedImage(strImgFileLower, frameSpacing, [&progressDialog](
int prog) {
96 progressDialog.setValue(prog);
98 }, [&progressDialog]() {
99 return progressDialog.wasCanceled();
102 progressDialog.setValue(100);
103 progressDialog.close();
107 ErrorDialog errorDialog(st.title(), st.description(), st.details().html());
115Status ActionCommands::importMovieVideo()
125 hideQuestionMark(progressDialog);
127 progressDialog.setMinimumWidth(250);
128 progressDialog.show();
132 information.setText(
tr(
"You are importing a lot of frames, beware this could take some time. Are you sure you want to proceed?"));
137 importer.setCore(mEditor);
141 Status st = importer.run(filePath, mEditor->playback()->fps(), FileType::MOVIE, [&progressDialog](
int prog) {
142 progressDialog.setValue(prog);
143 QApplication::processEvents();
144 }, [&progressDialog](
QString progMessage) {
145 progressDialog.setLabelText(progMessage);
146 }, [&information]() {
148 int ret = information.exec();
149 return ret == QMessageBox::Yes;
152 if (!st.ok() && st != Status::CANCELED)
154 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
162 progressDialog.setValue(100);
163 progressDialog.close();
168Status ActionCommands::importSound(FileType type)
170 Layer* layer = mEditor->layers()->currentLayer();
171 if (layer ==
nullptr)
177 if (layer->type() != Layer::SOUND)
180 msg.
setText(
tr(
"No sound layer exists as a destination for your import. Create a new sound layer?"));
184 int buttonClicked = msg.
exec();
194 mEditor->layers()->nameSuggestLayer(
tr(
"Sound Layer",
"Default name on creating a sound layer")), &ok);
195 if (ok && !strLayerName.
isEmpty())
197 Layer* newLayer = mEditor->layers()->createSoundLayer(strLayerName);
198 mEditor->layers()->setCurrentLayer(newLayer);
206 layer = mEditor->layers()->currentLayer();
207 Q_ASSERT(layer->type() == Layer::SOUND);
225 st = Status::CANCELED;
231 st = convertSoundToWav(strSoundFile);
236 mEditor->removeKey();
237 emit mEditor->layers()->currentLayerChanged(mEditor->layers()->currentLayerIndex());
239 showSoundClipWarningIfNeeded();
245Status ActionCommands::convertSoundToWav(
const QString& filePath)
248 hideQuestionMark(progressDialog);
250 progressDialog.show();
253 importer.setCore(mEditor);
255 Status st = importer.run(filePath, mEditor->playback()->fps(), FileType::SOUND, [&progressDialog](
int prog) {
256 progressDialog.setValue(prog);
257 QApplication::processEvents();
258 }, [](
QString progressMessage) {
259 Q_UNUSED(progressMessage)
267 if (!st.ok() && st != Status::CANCELED)
269 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
275Status ActionCommands::exportGif()
278 return exportMovie(
true);
281Status ActionCommands::exportMovie(
bool isGif)
283 FileType fileType = (isGif) ? FileType::GIF : FileType::MOVIE;
285 int clipCount = mEditor->sound()->soundClipCount();
286 if (fileType == FileType::MOVIE && clipCount >= MovieExporter::MAX_SOUND_FRAMES)
288 ErrorDialog errorDialog(
tr(
"Something went wrong"),
tr(
"You currently have a total of %1 sound clips. Due to current limitations, you will be unable to export any animation exceeding %2 sound clips. We recommend splitting up larger projects into multiple smaller project to stay within this limit.").arg(clipCount).arg(MovieExporter::MAX_SOUND_FRAMES),
QString(), mParent);
298 std::vector< std::pair<QString, QSize> > camerasInfo;
299 auto cameraLayers = mEditor->object()->getLayersByType<
LayerCamera >();
302 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
305 auto currLayer = mEditor->layers()->currentLayer();
306 if (currLayer->type() == Layer::CAMERA)
308 QString strName = currLayer->name();
309 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
310 [strName](std::pair<QString, QSize> p)
312 return p.first == strName;
315 Q_ASSERT(it != camerasInfo.end());
317 std::swap(camerasInfo[0], *it);
320 dialog->setCamerasInfo(camerasInfo);
325 dialog->setDefaultRange(1, length, lengthWithSounds);
332 QString strMoviePath = dialog->getFilePath();
335 desc.strFileName = strMoviePath;
336 desc.startFrame = dialog->getStartFrame();
337 desc.endFrame = dialog->getEndFrame();
338 desc.fps = mEditor->playback()->fps();
339 desc.exportSize = dialog->getExportSize();
340 desc.strCameraName = dialog->getSelectedCameraName();
341 desc.loop = dialog->getLoop();
342 desc.alpha = dialog->getTransparency();
346 progressDlg.setWindowTitle(
tr(
"Exporting movie"));
348 progressDlg.setWindowFlags(eFlags);
359 float minorStart, minorLength;
361 Status st = ex.
run(mEditor->object(), desc,
362 [&progressDlg, &minorStart, &minorLength](
float f,
float final)
364 progressDlg.major->setValue(f);
367 minorLength = qMax(0.f, final - minorStart);
369 QApplication::processEvents();
371 [&progressDlg, &minorStart, &minorLength](
float f) {
372 progressDlg.minor->setValue(f);
374 progressDlg.major->setValue(minorStart + f * minorLength);
376 QApplication::processEvents();
379 progressDlg.setStatus(s);
380 QApplication::processEvents();
386 if (QFile::exists(strMoviePath))
390 tr(
"Finished. Open file location?"));
394 QString path = dialog->getAbsolutePath();
400 tr(
"Finished. Open movie now?",
"When movie export done."));
408 ErrorDialog errorDialog(
tr(
"Unknown export error"),
tr(
"The export did not produce any errors, however we can't find the output file. Your export may not have completed successfully."),
QString(), mParent);
412 else if(st != Status::CANCELED)
414 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
421Status ActionCommands::exportImageSequence()
428 std::vector< std::pair<QString, QSize> > camerasInfo;
429 auto cameraLayers = mEditor->object()->getLayersByType<
LayerCamera >();
432 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
435 auto currLayer = mEditor->layers()->currentLayer();
436 if (currLayer->type() == Layer::CAMERA)
438 QString strName = currLayer->name();
439 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
440 [strName](std::pair<QString, QSize> p)
442 return p.first == strName;
445 Q_ASSERT(it != camerasInfo.end());
446 std::swap(camerasInfo[0], *it);
448 dialog->setCamerasInfo(camerasInfo);
453 dialog->setDefaultRange(1, length, lengthWithSounds);
462 QString strFilePath = dialog->getFilePath();
463 QSize exportSize = dialog->getExportSize();
464 QString exportFormat = dialog->getExportFormat();
465 bool exportKeyframesOnly = dialog->getExportKeyframesOnly();
466 bool useTranparency = dialog->getTransparency();
467 int startFrame = dialog->getStartFrame();
468 int endFrame = dialog->getEndFrame();
470 QString sCameraLayerName = dialog->getCameraLayerName();
471 LayerCamera* cameraLayer =
static_cast<LayerCamera*
>(mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA));
475 hideQuestionMark(progress);
479 mEditor->object()->exportFrames(startFrame, endFrame,
486 mEditor->layers()->currentLayer()->name(),
496Status ActionCommands::exportImage()
505 auto cameraLayers = mEditor->
object()->getLayersByType<
LayerCamera >();
508 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
511 auto currLayer = mEditor->layers()->currentLayer();
512 if (currLayer->type() == Layer::CAMERA)
514 QString strName = currLayer->name();
515 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
516 [strName](std::pair<QString, QSize> p)
518 return p.first == strName;
521 Q_ASSERT(it != camerasInfo.end());
522 std::swap(camerasInfo[0], *it);
524 dialog->setCamerasInfo(camerasInfo);
533 QString filePath = dialog->getFilePath();
534 QSize exportSize = dialog->getExportSize();
535 QString exportFormat = dialog->getExportFormat();
536 bool useTranparency = dialog->getTransparency();
539 QString sCameraLayerName = dialog->getCameraLayerName();
540 LayerCamera* cameraLayer =
static_cast<LayerCamera*
>(mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA));
542 QTransform view = cameraLayer->getViewAtFrame(mEditor->currentFrame());
544 bool bOK = mEditor->object()->exportIm(mEditor->currentFrame(),
546 cameraLayer->getViewSize(),
557 tr(
"Unable to export image."),
564void ActionCommands::flipSelectionX()
566 bool flipVertical =
false;
567 mEditor->flipSelection(flipVertical);
570void ActionCommands::flipSelectionY()
572 bool flipVertical =
true;
573 mEditor->flipSelection(flipVertical);
576void ActionCommands::selectAll()
578 mEditor->selectAll();
581void ActionCommands::deselectAll()
583 mEditor->deselectAll();
586void ActionCommands::ZoomIn()
588 mEditor->view()->scaleUp();
591void ActionCommands::ZoomOut()
593 mEditor->view()->scaleDown();
596void ActionCommands::rotateClockwise()
599 const float delta = mEditor->view()->isFlipHorizontal() == !mEditor->view()->isFlipVertical() ? -15.f : 15.f;
600 mEditor->view()->rotateRelative(delta);
603void ActionCommands::rotateCounterClockwise()
606 const float delta = mEditor->view()->isFlipHorizontal() == !mEditor->view()->isFlipVertical() ? 15.f : -15.f;
607 mEditor->view()->rotateRelative(delta);
610void ActionCommands::PlayStop()
613 if (playback->isPlaying())
623void ActionCommands::GotoNextFrame()
625 mEditor->scrubForward();
628void ActionCommands::GotoPrevFrame()
630 mEditor->scrubBackward();
633void ActionCommands::GotoNextKeyFrame()
635 mEditor->scrubNextKeyFrame();
638void ActionCommands::GotoPrevKeyFrame()
640 mEditor->scrubPreviousKeyFrame();
643Status ActionCommands::addNewKey()
646 if (mEditor->layers()->currentLayer()->type() == Layer::SOUND)
648 return importSound(FileType::SOUND);
655 mEditor->view()->forceUpdateViewTransform();
661void ActionCommands::exposeSelectedFrames(
int offset)
663 Layer* currentLayer = mEditor->layers()->currentLayer();
665 bool hasSelectedFrames = currentLayer->hasAnySelectedFrames();
669 KeyFrame* key = currentLayer->getLastKeyFrameAtPosition(mEditor->currentFrame());
670 if (!hasSelectedFrames) {
672 if (key ==
nullptr) {
return; }
673 currentLayer->setFrameSelected(key->pos(),
true);
677 emit mEditor->updateTimeLine();
682 if (!hasSelectedFrames) {
683 currentLayer->setFrameSelected(key->pos(),
false);
687void ActionCommands::addExposureToSelectedFrames()
689 exposeSelectedFrames(1);
692void ActionCommands::subtractExposureFromSelectedFrames()
694 exposeSelectedFrames(-1);
699 Layer* currentLayer = mEditor->layers()->currentLayer();
700 int currentPosition = mEditor->currentFrame();
706void ActionCommands::removeSelectedFrames()
708 Layer* currentLayer = mEditor->layers()->currentLayer();
710 if (!currentLayer->hasAnySelectedFrames()) {
return; }
713 tr(
"Remove selected frames",
"Windows title of remove selected frames pop-up."),
714 tr(
"Are you sure you want to remove the selected frames? This action is irreversible currently!"),
724 currentLayer->removeKeyFrame(pos);
726 mEditor->layers()->notifyLayerChanged(currentLayer);
729void ActionCommands::reverseSelectedFrames()
731 Layer* currentLayer = mEditor->layers()->currentLayer();
737 if (currentLayer->type() == Layer::CAMERA) {
738 mEditor->view()->forceUpdateViewTransform();
743void ActionCommands::removeKey()
745 mEditor->removeKey();
748void ActionCommands::duplicateLayer()
751 Layer* fromLayer = layerMgr->currentLayer();
752 int currFrame = mEditor->currentFrame();
754 Layer* toLayer = layerMgr->
createLayer(fromLayer->type(),
tr(
"%1 (copy)",
"Default duplicate layer name").arg(fromLayer->name()));
755 fromLayer->foreachKeyFrame([&] (
KeyFrame* key) {
757 toLayer->addOrReplaceKeyFrame(key->pos(), key);
758 if (toLayer->type() == Layer::SOUND)
760 mEditor->sound()->processSound(static_cast<SoundClip*>(key));
763 if (!fromLayer->keyExists(1)) {
764 toLayer->removeKeyFrame(1);
766 mEditor->scrubTo(currFrame);
769void ActionCommands::duplicateKey()
771 Layer* layer = mEditor->layers()->currentLayer();
772 if (layer ==
nullptr)
return;
773 if (!layer->visible())
775 mEditor->getScribbleArea()->showLayerNotVisibleWarning();
779 KeyFrame* key = layer->getKeyFrameAt(mEditor->currentFrame());
780 if (key ==
nullptr)
return;
788 int nextEmptyFrame = mEditor->currentFrame() + 1;
789 while (layer->keyExistsWhichCovers(nextEmptyFrame))
795 mEditor->scrubTo(nextEmptyFrame);
798 if (layer->type() == Layer::SOUND)
800 mEditor->sound()->processSound(
dynamic_cast<SoundClip*
>(dupKey));
801 showSoundClipWarningIfNeeded();
805 emit mEditor->layers()->currentLayerChanged(mEditor->layers()->currentLayerIndex());
808void ActionCommands::moveFrameForward()
810 Layer* layer = mEditor->layers()->currentLayer();
813 if (layer->moveKeyFrame(mEditor->currentFrame(), 1))
815 mEditor->scrubForward();
822void ActionCommands::moveFrameBackward()
824 Layer* layer = mEditor->layers()->currentLayer();
827 if (layer->moveKeyFrame(mEditor->currentFrame(), -1))
829 mEditor->scrubBackward();
835Status ActionCommands::addNewBitmapLayer()
840 mEditor->layers()->nameSuggestLayer(
tr(
"Bitmap Layer")), &ok);
843 mEditor->layers()->createBitmapLayer(text);
848Status ActionCommands::addNewVectorLayer()
853 mEditor->layers()->nameSuggestLayer(
tr(
"Vector Layer")), &ok);
856 mEditor->layers()->createVectorLayer(text);
861Status ActionCommands::addNewCameraLayer()
866 mEditor->layers()->nameSuggestLayer(
tr(
"Camera Layer")), &ok);
869 mEditor->layers()->createCameraLayer(text);
874Status ActionCommands::addNewSoundLayer()
879 mEditor->layers()->nameSuggestLayer(
tr(
"Sound Layer")), &ok);
880 if (ok && !strLayerName.
isEmpty())
882 Layer* layer = mEditor->layers()->createSoundLayer(strLayerName);
883 mEditor->layers()->setCurrentLayer(layer);
888Status ActionCommands::deleteCurrentLayer()
891 QString strLayerName = layerMgr->currentLayer()->name();
893 if (!layerMgr->canDeleteLayer(mEditor->currentLayerIndex())) {
894 return Status::CANCELED;
898 tr(
"Delete Layer",
"Windows title of Delete current layer pop-up."),
899 tr(
"Are you sure you want to delete layer: %1? This cannot be undone.").arg(strLayerName),
904 Status st = layerMgr->deleteLayer(mEditor->currentLayerIndex());
905 if (st == Status::ERROR_NEED_AT_LEAST_ONE_CAMERA_LAYER)
908 tr(
"Please keep at least one camera layer in project",
"text when failed to delete camera layer"));
914void ActionCommands::setLayerVisibilityIndex(
int index)
919void ActionCommands::changeKeyframeLineColor()
921 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP &&
922 mEditor->layers()->currentLayer()->keyExists(mEditor->currentFrame()))
926 layer->getBitmapImageAtFrame(mEditor->currentFrame())->fillNonAlphaPixels(color);
931void ActionCommands::changeallKeyframeLineColor()
933 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP)
937 for (
int i = layer->firstKeyFramePosition(); i <= layer->getMaxKeyFramePosition(); i++)
939 if (layer->keyExists(i))
940 layer->getBitmapImageAtFrame(i)->fillNonAlphaPixels(color);
947void ActionCommands::resetAllTools()
949 mEditor->tools()->resetAllTools();
952void ActionCommands::help()
954 QString url =
"http://www.pencil2d.org/doc/";
958void ActionCommands::quickGuide()
963 QFile quickGuideFile(
":/app/pencil2d_quick_guide.pdf");
964 quickGuideFile.copy(sCopyDest);
969void ActionCommands::website()
971 QString url =
"https://www.pencil2d.org/";
975void ActionCommands::forum()
977 QString url =
"https://discuss.pencil2d.org/";
981void ActionCommands::discord()
983 QString url =
"https://discord.gg/8FxdV2g";
987void ActionCommands::reportbug()
989 QString url =
"https://github.com/pencil2d/pencil/issues";
993void ActionCommands::checkForUpdates()
996 dialog.startChecking();
1001void ActionCommands::openTemporaryDirectory()
1010void ActionCommands::about()
1018void ActionCommands::showSoundClipWarningIfNeeded()
1020 int clipCount = mEditor->sound()->soundClipCount();
1021 if (clipCount >= MovieExporter::MAX_SOUND_FRAMES && !mSuppressSoundWarning) {
1022 QMessageBox::warning(mParent,
tr(
"Warning"),
tr(
"You currently have a total of %1 sound clips. Due to current limitations, you will be unable to export any animation exceeding %2 sound clips. We recommend splitting up larger projects into multiple smaller project to stay within this limit.").arg(clipCount).arg(MovieExporter::MAX_SOUND_FRAMES));
1023 mSuppressSoundWarning =
true;
1025 mSuppressSoundWarning =
false;
Status insertKeyFrameAtCurrentPosition()
Will insert a keyframe at the current position and push connected frames to the right.
QColor frontColor(bool useIndexedColor=true)
frontColor
void framesModified()
This should be emitted after modifying multiple frames.
KeyFrame * addNewKey()
Attempts to create a new keyframe at the current frame and layer.
void frameModified(int frameNumber)
This should be emitted after modifying the frame content.
void updateFrame()
Will call update() and update the canvas Only call this directly If you need the cache to be intact a...
void setLayerVisibility(LayerVisibility visibility)
The visibility value should match any of the VISIBILITY enum values.
static QString getOpenFileName(QWidget *parent, FileType fileType, const QString &caption=QString())
Shows a file dialog which allows the user to select a file to open.
QList< int > selectedKeyFramesPositions() const
Get selected keyframe positions sorted by position.
bool reverseOrderOfSelection()
Reverse order of selected frames.
virtual bool addKeyFrame(int position, KeyFrame *pKeyFrame)
Adds a keyframe at the given position, unless one already exists.
void setExposureForSelectedFrames(int offset)
Add or subtract exposure from selected frames.
bool insertExposureAt(int position)
Will insert an empty frame (exposure) after the given position.
int animationLength(bool includeSounds=true)
Get the length of current project.
Layer * createLayer(Layer::LAYER_TYPE type, const QString &strLayerName)
Returns a new Layer with the given LAYER_TYPE.
void notifyAnimationLengthChanged()
This should be emitted whenever the animation length frames, eg.
Status run(const Object *obj, const ExportMovieDesc &desc, std::function< void(float, float)> majorProgress, std::function< void(float)> minorProgress, std::function< void(QString)> progressMessage)
Begin exporting the movie described by exportDesc.
void processEvents(QEventLoop::ProcessEventsFlags flags)
bool openUrl(const QUrl &url)
QString filePath(const QString &fileName) const const
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
virtual int exec() override
QMessageBox::StandardButton question(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
void setText(const QString &text)
QMessageBox::StandardButton warning(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString tr(const char *sourceText, const char *disambiguation, int n)
QString writableLocation(QStandardPaths::StandardLocation type)
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)