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 "soundmanager.h"
34#include "playbackmanager.h"
35#include "colormanager.h"
36#include "preferencemanager.h"
37#include "selectionmanager.h"
41#include "layercamera.h"
42#include "layersound.h"
43#include "layerbitmap.h"
44#include "layervector.h"
45#include "bitmapimage.h"
46#include "vectorimage.h"
50#include "importimageseqdialog.h"
51#include "importpositiondialog.h"
52#include "movieimporter.h"
53#include "movieexporter.h"
54#include "filedialog.h"
55#include "exportmoviedialog.h"
56#include "exportimagedialog.h"
57#include "aboutdialog.h"
58#include "doubleprogressdialog.h"
59#include "checkupdatesdialog.h"
60#include "errordialog.h"
68ActionCommands::~ActionCommands() {}
70Status ActionCommands::importAnimatedImage()
76 return Status::CANCELED;
78 int frameSpacing = fileDialog.getSpace();
79 QString strImgFileLower = fileDialog.getFilePath();
82 positionDialog.exec();
85 return Status::CANCELED;
89 QProgressDialog progressDialog(
tr(
"Importing Animated Image..."),
tr(
"Abort"), 0, 100, mParent);
90 hideQuestionMark(progressDialog);
92 progressDialog.show();
94 Status st = mEditor->importAnimatedImage(strImgFileLower, frameSpacing, [&progressDialog](
int prog) {
95 progressDialog.setValue(prog);
97 }, [&progressDialog]() {
98 return progressDialog.wasCanceled();
101 progressDialog.setValue(100);
102 progressDialog.close();
106 ErrorDialog errorDialog(st.title(), st.description(), st.details().html());
114Status ActionCommands::importMovieVideo()
124 hideQuestionMark(progressDialog);
126 progressDialog.setMinimumWidth(250);
127 progressDialog.show();
131 information.setText(
tr(
"You are importing a lot of frames, beware this could take some time. Are you sure you want to proceed?"));
136 importer.setCore(mEditor);
140 Status st = importer.run(filePath, mEditor->playback()->fps(), FileType::MOVIE, [&progressDialog](
int prog) {
141 progressDialog.setValue(prog);
142 QApplication::processEvents();
143 }, [&progressDialog](
QString progMessage) {
144 progressDialog.setLabelText(progMessage);
145 }, [&information]() {
147 int ret = information.exec();
148 return ret == QMessageBox::Yes;
151 if (!st.ok() && st != Status::CANCELED)
153 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
161 progressDialog.setValue(100);
162 progressDialog.close();
167Status ActionCommands::importSound(FileType type)
169 Layer* layer = mEditor->layers()->currentLayer();
170 if (layer ==
nullptr)
176 if (layer->type() != Layer::SOUND)
179 msg.
setText(
tr(
"No sound layer exists as a destination for your import. Create a new sound layer?"));
183 int buttonClicked = msg.
exec();
193 mEditor->layers()->nameSuggestLayer(
tr(
"Sound Layer",
"Default name on creating a sound layer")), &ok);
194 if (ok && !strLayerName.
isEmpty())
196 Layer* newLayer = mEditor->layers()->createSoundLayer(strLayerName);
197 mEditor->layers()->setCurrentLayer(newLayer);
205 layer = mEditor->layers()->currentLayer();
206 Q_ASSERT(layer->type() == Layer::SOUND);
224 st = Status::CANCELED;
230 st = convertSoundToWav(strSoundFile);
235 mEditor->removeKey();
236 emit mEditor->layers()->currentLayerChanged(mEditor->layers()->currentLayerIndex());
238 showSoundClipWarningIfNeeded();
244Status ActionCommands::convertSoundToWav(
const QString& filePath)
247 hideQuestionMark(progressDialog);
249 progressDialog.show();
252 importer.setCore(mEditor);
254 Status st = importer.run(filePath, mEditor->playback()->fps(), FileType::SOUND, [&progressDialog](
int prog) {
255 progressDialog.setValue(prog);
256 QApplication::processEvents();
257 }, [](
QString progressMessage) {
258 Q_UNUSED(progressMessage)
266 if (!st.ok() && st != Status::CANCELED)
268 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
274Status ActionCommands::exportGif()
277 return exportMovie(
true);
280Status ActionCommands::exportMovie(
bool isGif)
282 FileType fileType = (isGif) ? FileType::GIF : FileType::MOVIE;
284 int clipCount = mEditor->sound()->soundClipCount();
285 if (fileType == FileType::MOVIE && clipCount >= MovieExporter::MAX_SOUND_FRAMES)
287 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);
297 std::vector< std::pair<QString, QSize> > camerasInfo;
298 auto cameraLayers = mEditor->object()->getLayersByType<
LayerCamera >();
301 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
304 auto currLayer = mEditor->layers()->currentLayer();
305 if (currLayer->type() == Layer::CAMERA)
307 QString strName = currLayer->name();
308 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
309 [strName](std::pair<QString, QSize> p)
311 return p.first == strName;
314 Q_ASSERT(it != camerasInfo.end());
316 std::swap(camerasInfo[0], *it);
319 dialog->setCamerasInfo(camerasInfo);
324 dialog->setDefaultRange(1, length, lengthWithSounds);
331 QString strMoviePath = dialog->getFilePath();
334 desc.strFileName = strMoviePath;
335 desc.startFrame = dialog->getStartFrame();
336 desc.endFrame = dialog->getEndFrame();
337 desc.fps = mEditor->playback()->fps();
338 desc.exportSize = dialog->getExportSize();
339 desc.strCameraName = dialog->getSelectedCameraName();
340 desc.loop = dialog->getLoop();
341 desc.alpha = dialog->getTransparency();
345 progressDlg.setWindowTitle(
tr(
"Exporting movie"));
347 progressDlg.setWindowFlags(eFlags);
358 float minorStart, minorLength;
360 Status st = ex.
run(mEditor->object(), desc,
361 [&progressDlg, &minorStart, &minorLength](
float f,
float final)
363 progressDlg.major->setValue(f);
366 minorLength = qMax(0.f, final - minorStart);
368 QApplication::processEvents();
370 [&progressDlg, &minorStart, &minorLength](
float f) {
371 progressDlg.minor->setValue(f);
373 progressDlg.major->setValue(minorStart + f * minorLength);
375 QApplication::processEvents();
378 progressDlg.setStatus(s);
379 QApplication::processEvents();
389 tr(
"Finished. Open file location?"));
393 QString path = dialog->getAbsolutePath();
399 tr(
"Finished. Open movie now?",
"When movie export done."));
407 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);
411 else if(st != Status::CANCELED)
413 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
420Status ActionCommands::exportImageSequence()
427 std::vector< std::pair<QString, QSize> > camerasInfo;
428 auto cameraLayers = mEditor->object()->getLayersByType<
LayerCamera >();
431 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
434 auto currLayer = mEditor->layers()->currentLayer();
435 if (currLayer->type() == Layer::CAMERA)
437 QString strName = currLayer->name();
438 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
439 [strName](std::pair<QString, QSize> p)
441 return p.first == strName;
444 Q_ASSERT(it != camerasInfo.end());
445 std::swap(camerasInfo[0], *it);
447 dialog->setCamerasInfo(camerasInfo);
452 dialog->setDefaultRange(1, length, lengthWithSounds);
461 QString strFilePath = dialog->getFilePath();
462 QSize exportSize = dialog->getExportSize();
463 QString exportFormat = dialog->getExportFormat();
464 bool exportKeyframesOnly = dialog->getExportKeyframesOnly();
465 bool useTranparency = dialog->getTransparency();
466 int startFrame = dialog->getStartFrame();
467 int endFrame = dialog->getEndFrame();
469 QString sCameraLayerName = dialog->getCameraLayerName();
470 LayerCamera* cameraLayer =
static_cast<LayerCamera*
>(mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA));
474 hideQuestionMark(progress);
478 mEditor->object()->exportFrames(startFrame, endFrame,
485 mEditor->layers()->currentLayer()->name(),
495Status ActionCommands::exportImage()
504 auto cameraLayers = mEditor->
object()->getLayersByType<
LayerCamera >();
507 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
510 auto currLayer = mEditor->layers()->currentLayer();
511 if (currLayer->type() == Layer::CAMERA)
513 QString strName = currLayer->name();
514 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
515 [strName](std::pair<QString, QSize> p)
517 return p.first == strName;
520 Q_ASSERT(it != camerasInfo.end());
521 std::swap(camerasInfo[0], *it);
523 dialog->setCamerasInfo(camerasInfo);
532 QString filePath = dialog->getFilePath();
533 QSize exportSize = dialog->getExportSize();
534 QString exportFormat = dialog->getExportFormat();
535 bool useTranparency = dialog->getTransparency();
538 QString sCameraLayerName = dialog->getCameraLayerName();
539 LayerCamera* cameraLayer =
static_cast<LayerCamera*
>(mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA));
541 QTransform view = cameraLayer->getViewAtFrame(mEditor->currentFrame());
543 bool bOK = mEditor->object()->exportIm(mEditor->currentFrame(),
545 cameraLayer->getViewSize(),
556 tr(
"Unable to export image."),
563void ActionCommands::flipSelectionX()
565 bool flipVertical =
false;
566 mEditor->flipSelection(flipVertical);
569void ActionCommands::flipSelectionY()
571 bool flipVertical =
true;
572 mEditor->flipSelection(flipVertical);
575void ActionCommands::selectAll()
577 mEditor->selectAll();
580void ActionCommands::deselectAll()
582 mEditor->deselectAll();
585void ActionCommands::ZoomIn()
587 mEditor->view()->scaleUp();
590void ActionCommands::ZoomOut()
592 mEditor->view()->scaleDown();
595void ActionCommands::rotateClockwise()
598 const float delta = mEditor->view()->isFlipHorizontal() == !mEditor->view()->isFlipVertical() ? -15.f : 15.f;
599 mEditor->view()->rotateRelative(delta);
602void ActionCommands::rotateCounterClockwise()
605 const float delta = mEditor->view()->isFlipHorizontal() == !mEditor->view()->isFlipVertical() ? 15.f : -15.f;
606 mEditor->view()->rotateRelative(delta);
609void ActionCommands::PlayStop()
612 if (playback->isPlaying())
622void ActionCommands::GotoNextFrame()
624 mEditor->scrubForward();
627void ActionCommands::GotoPrevFrame()
629 mEditor->scrubBackward();
632void ActionCommands::GotoNextKeyFrame()
634 mEditor->scrubNextKeyFrame();
637void ActionCommands::GotoPrevKeyFrame()
639 mEditor->scrubPreviousKeyFrame();
642Status ActionCommands::addNewKey()
645 if (mEditor->layers()->currentLayer()->type() == Layer::SOUND)
647 return importSound(FileType::SOUND);
654 mEditor->view()->forceUpdateViewTransform();
660void ActionCommands::exposeSelectedFrames(
int offset)
662 Layer* currentLayer = mEditor->layers()->currentLayer();
664 bool hasSelectedFrames = currentLayer->hasAnySelectedFrames();
668 KeyFrame* key = currentLayer->getLastKeyFrameAtPosition(mEditor->currentFrame());
669 if (!hasSelectedFrames) {
671 if (key ==
nullptr) {
return; }
672 currentLayer->setFrameSelected(key->pos(),
true);
676 emit mEditor->updateTimeLine();
681 if (!hasSelectedFrames) {
682 currentLayer->setFrameSelected(key->pos(),
false);
686void ActionCommands::addExposureToSelectedFrames()
688 exposeSelectedFrames(1);
691void ActionCommands::subtractExposureFromSelectedFrames()
693 exposeSelectedFrames(-1);
698 Layer* currentLayer = mEditor->layers()->currentLayer();
699 int currentPosition = mEditor->currentFrame();
705void ActionCommands::removeSelectedFrames()
707 Layer* currentLayer = mEditor->layers()->currentLayer();
709 if (!currentLayer->hasAnySelectedFrames()) {
return; }
712 tr(
"Remove selected frames",
"Windows title of remove selected frames pop-up."),
713 tr(
"Are you sure you want to remove the selected frames? This action is irreversible currently!"),
723 currentLayer->removeKeyFrame(pos);
725 mEditor->layers()->notifyLayerChanged(currentLayer);
728void ActionCommands::reverseSelectedFrames()
730 Layer* currentLayer = mEditor->layers()->currentLayer();
736 if (currentLayer->type() == Layer::CAMERA) {
737 mEditor->view()->forceUpdateViewTransform();
742void ActionCommands::removeKey()
744 mEditor->removeKey();
747void ActionCommands::duplicateLayer()
750 Layer* fromLayer = layerMgr->currentLayer();
751 int currFrame = mEditor->currentFrame();
753 Layer* toLayer = layerMgr->
createLayer(fromLayer->type(),
tr(
"%1 (copy)",
"Default duplicate layer name").arg(fromLayer->name()));
754 fromLayer->foreachKeyFrame([&] (
KeyFrame* key) {
756 toLayer->addOrReplaceKeyFrame(key->pos(), key);
757 if (toLayer->type() == Layer::SOUND)
759 mEditor->sound()->processSound(static_cast<SoundClip*>(key));
766 if (!fromLayer->keyExists(1)) {
767 toLayer->removeKeyFrame(1);
769 mEditor->scrubTo(currFrame);
772void ActionCommands::duplicateKey()
774 Layer* layer = mEditor->layers()->currentLayer();
775 if (layer ==
nullptr)
return;
776 if (!layer->visible())
778 mEditor->getScribbleArea()->showLayerNotVisibleWarning();
782 KeyFrame* key = layer->getKeyFrameAt(mEditor->currentFrame());
783 if (key ==
nullptr)
return;
791 int nextEmptyFrame = mEditor->currentFrame() + 1;
792 while (layer->keyExistsWhichCovers(nextEmptyFrame))
798 mEditor->scrubTo(nextEmptyFrame);
801 if (layer->type() == Layer::SOUND)
803 mEditor->sound()->processSound(
dynamic_cast<SoundClip*
>(dupKey));
804 showSoundClipWarningIfNeeded();
808 dupKey->setFileName(
"");
809 dupKey->modification();
813 emit mEditor->layers()->currentLayerChanged(mEditor->layers()->currentLayerIndex());
816void ActionCommands::moveFrameForward()
818 Layer* layer = mEditor->layers()->currentLayer();
821 if (layer->moveKeyFrame(mEditor->currentFrame(), 1))
823 mEditor->scrubForward();
830void ActionCommands::moveFrameBackward()
832 Layer* layer = mEditor->layers()->currentLayer();
835 if (layer->moveKeyFrame(mEditor->currentFrame(), -1))
837 mEditor->scrubBackward();
843Status ActionCommands::addNewBitmapLayer()
848 mEditor->layers()->nameSuggestLayer(
tr(
"Bitmap Layer")), &ok);
851 mEditor->layers()->createBitmapLayer(text);
856Status ActionCommands::addNewVectorLayer()
861 mEditor->layers()->nameSuggestLayer(
tr(
"Vector Layer")), &ok);
864 mEditor->layers()->createVectorLayer(text);
869Status ActionCommands::addNewCameraLayer()
874 mEditor->layers()->nameSuggestLayer(
tr(
"Camera Layer")), &ok);
877 mEditor->layers()->createCameraLayer(text);
882Status ActionCommands::addNewSoundLayer()
887 mEditor->layers()->nameSuggestLayer(
tr(
"Sound Layer")), &ok);
888 if (ok && !strLayerName.
isEmpty())
890 Layer* layer = mEditor->layers()->createSoundLayer(strLayerName);
891 mEditor->layers()->setCurrentLayer(layer);
896Status ActionCommands::deleteCurrentLayer()
899 QString strLayerName = layerMgr->currentLayer()->name();
901 if (!layerMgr->canDeleteLayer(mEditor->currentLayerIndex())) {
902 return Status::CANCELED;
906 tr(
"Delete Layer",
"Windows title of Delete current layer pop-up."),
907 tr(
"Are you sure you want to delete layer: %1? This cannot be undone.").arg(strLayerName),
912 Status st = layerMgr->deleteLayer(mEditor->currentLayerIndex());
913 if (st == Status::ERROR_NEED_AT_LEAST_ONE_CAMERA_LAYER)
916 tr(
"Please keep at least one camera layer in project",
"text when failed to delete camera layer"));
922void ActionCommands::setLayerVisibilityIndex(
int index)
927void ActionCommands::changeKeyframeLineColor()
929 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP &&
930 mEditor->layers()->currentLayer()->keyExists(mEditor->currentFrame()))
934 layer->getBitmapImageAtFrame(mEditor->currentFrame())->fillNonAlphaPixels(color);
939void ActionCommands::changeallKeyframeLineColor()
941 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP)
945 for (
int i = layer->firstKeyFramePosition(); i <= layer->getMaxKeyFramePosition(); i++)
947 if (layer->keyExists(i))
948 layer->getBitmapImageAtFrame(i)->fillNonAlphaPixels(color);
954void ActionCommands::help()
956 QString url =
"http://www.pencil2d.org/doc/";
960void ActionCommands::quickGuide()
965 QFile quickGuideFile(
":/app/pencil2d_quick_guide.pdf");
966 quickGuideFile.copy(sCopyDest);
971void ActionCommands::website()
973 QString url =
"https://www.pencil2d.org/";
977void ActionCommands::forum()
979 QString url =
"https://discuss.pencil2d.org/";
983void ActionCommands::discord()
985 QString url =
"https://discord.gg/8FxdV2g";
989void ActionCommands::reportbug()
991 QString url =
"https://github.com/pencil2d/pencil/issues";
995void ActionCommands::checkForUpdates()
998 dialog.startChecking();
1003void ActionCommands::openTemporaryDirectory()
1012void ActionCommands::about()
1020void ActionCommands::showSoundClipWarningIfNeeded()
1022 int clipCount = mEditor->sound()->soundClipCount();
1023 if (clipCount >= MovieExporter::MAX_SOUND_FRAMES && !mSuppressSoundWarning) {
1024 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));
1025 mSuppressSoundWarning =
true;
1027 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
bool exists() 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)