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 "selectionmanager.h"
41#include "layercamera.h"
42#include "layersound.h"
43#include "layerbitmap.h"
44#include "bitmapimage.h"
45#include "vectorimage.h"
49#include "importimageseqdialog.h"
50#include "importpositiondialog.h"
51#include "movieimporter.h"
52#include "movieexporter.h"
53#include "filedialog.h"
54#include "exportmoviedialog.h"
55#include "exportimagedialog.h"
56#include "aboutdialog.h"
57#include "doubleprogressdialog.h"
58#include "checkupdatesdialog.h"
59#include "errordialog.h"
67ActionCommands::~ActionCommands() {}
69Status ActionCommands::importAnimatedImage()
75 return Status::CANCELED;
77 int frameSpacing = fileDialog.getSpace();
78 QString strImgFileLower = fileDialog.getFilePath();
81 positionDialog.exec();
84 return Status::CANCELED;
88 QProgressDialog progressDialog(
tr(
"Importing Animated Image..."),
tr(
"Abort"), 0, 100, mParent);
89 hideQuestionMark(progressDialog);
91 progressDialog.show();
93 Status st = mEditor->importAnimatedImage(strImgFileLower, frameSpacing, [&progressDialog](
int prog) {
94 progressDialog.setValue(prog);
96 }, [&progressDialog]() {
97 return progressDialog.wasCanceled();
100 progressDialog.setValue(100);
101 progressDialog.close();
105 ErrorDialog errorDialog(st.title(), st.description(), st.details().html());
113Status ActionCommands::importMovieVideo()
123 hideQuestionMark(progressDialog);
125 progressDialog.setMinimumWidth(250);
126 progressDialog.show();
130 information.setText(
tr(
"You are importing a lot of frames, beware this could take some time. Are you sure you want to proceed?"));
135 importer.setCore(mEditor);
139 Status st = importer.run(filePath, mEditor->playback()->fps(), FileType::MOVIE, [&progressDialog](
int prog) {
140 progressDialog.setValue(prog);
141 QApplication::processEvents();
142 }, [&progressDialog](
QString progMessage) {
143 progressDialog.setLabelText(progMessage);
144 }, [&information]() {
146 int ret = information.exec();
147 return ret == QMessageBox::Yes;
150 if (!st.ok() && st != Status::CANCELED)
152 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
160 progressDialog.setValue(100);
161 progressDialog.close();
166Status ActionCommands::importSound(FileType type)
168 Layer* layer = mEditor->layers()->currentLayer();
169 if (layer ==
nullptr)
175 if (layer->type() != Layer::SOUND)
178 msg.
setText(
tr(
"No sound layer exists as a destination for your import. Create a new sound layer?"));
182 int buttonClicked = msg.
exec();
192 mEditor->layers()->nameSuggestLayer(
tr(
"Sound Layer",
"Default name on creating a sound layer")), &ok);
193 if (ok && !strLayerName.
isEmpty())
195 Layer* newLayer = mEditor->layers()->createSoundLayer(strLayerName);
196 mEditor->layers()->setCurrentLayer(newLayer);
204 layer = mEditor->layers()->currentLayer();
205 Q_ASSERT(layer->type() == Layer::SOUND);
223 st = Status::CANCELED;
229 st = convertSoundToWav(strSoundFile);
234 mEditor->removeKey();
235 emit mEditor->layers()->currentLayerChanged(mEditor->layers()->currentLayerIndex());
237 showSoundClipWarningIfNeeded();
243Status ActionCommands::convertSoundToWav(
const QString& filePath)
246 hideQuestionMark(progressDialog);
248 progressDialog.show();
251 importer.setCore(mEditor);
253 Status st = importer.run(filePath, mEditor->playback()->fps(), FileType::SOUND, [&progressDialog](
int prog) {
254 progressDialog.setValue(prog);
255 QApplication::processEvents();
256 }, [](
QString progressMessage) {
257 Q_UNUSED(progressMessage)
265 if (!st.ok() && st != Status::CANCELED)
267 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
273Status ActionCommands::exportGif()
276 return exportMovie(
true);
279Status ActionCommands::exportMovie(
bool isGif)
281 FileType fileType = (isGif) ? FileType::GIF : FileType::MOVIE;
283 int clipCount = mEditor->sound()->soundClipCount();
284 if (fileType == FileType::MOVIE && clipCount >= MovieExporter::MAX_SOUND_FRAMES)
286 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);
296 std::vector< std::pair<QString, QSize> > camerasInfo;
297 auto cameraLayers = mEditor->object()->getLayersByType<
LayerCamera >();
300 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
303 auto currLayer = mEditor->layers()->currentLayer();
304 if (currLayer->type() == Layer::CAMERA)
306 QString strName = currLayer->name();
307 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
308 [strName](std::pair<QString, QSize> p)
310 return p.first == strName;
313 Q_ASSERT(it != camerasInfo.end());
315 std::swap(camerasInfo[0], *it);
318 dialog->setCamerasInfo(camerasInfo);
323 dialog->setDefaultRange(1, length, lengthWithSounds);
330 QString strMoviePath = dialog->getFilePath();
333 desc.strFileName = strMoviePath;
334 desc.startFrame = dialog->getStartFrame();
335 desc.endFrame = dialog->getEndFrame();
336 desc.fps = mEditor->playback()->fps();
337 desc.exportSize = dialog->getExportSize();
338 desc.strCameraName = dialog->getSelectedCameraName();
339 desc.loop = dialog->getLoop();
340 desc.alpha = dialog->getTransparency();
344 progressDlg.setWindowTitle(
tr(
"Exporting movie"));
346 progressDlg.setWindowFlags(eFlags);
357 float minorStart, minorLength;
359 Status st = ex.
run(mEditor->object(), desc,
360 [&progressDlg, &minorStart, &minorLength](
float f,
float final)
362 progressDlg.major->setValue(f);
365 minorLength = qMax(0.f, final - minorStart);
367 QApplication::processEvents();
369 [&progressDlg, &minorStart, &minorLength](
float f) {
370 progressDlg.minor->setValue(f);
372 progressDlg.major->setValue(minorStart + f * minorLength);
374 QApplication::processEvents();
377 progressDlg.setStatus(s);
378 QApplication::processEvents();
384 if (QFile::exists(strMoviePath))
388 tr(
"Finished. Open file location?"));
392 QString path = dialog->getAbsolutePath();
398 tr(
"Finished. Open movie now?",
"When movie export done."));
406 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);
410 else if(st != Status::CANCELED)
412 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
419Status ActionCommands::exportImageSequence()
426 std::vector< std::pair<QString, QSize> > camerasInfo;
427 auto cameraLayers = mEditor->object()->getLayersByType<
LayerCamera >();
430 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
433 auto currLayer = mEditor->layers()->currentLayer();
434 if (currLayer->type() == Layer::CAMERA)
436 QString strName = currLayer->name();
437 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
438 [strName](std::pair<QString, QSize> p)
440 return p.first == strName;
443 Q_ASSERT(it != camerasInfo.end());
444 std::swap(camerasInfo[0], *it);
446 dialog->setCamerasInfo(camerasInfo);
451 dialog->setDefaultRange(1, length, lengthWithSounds);
460 QString strFilePath = dialog->getFilePath();
461 QSize exportSize = dialog->getExportSize();
462 QString exportFormat = dialog->getExportFormat();
463 bool exportKeyframesOnly = dialog->getExportKeyframesOnly();
464 bool useTransparency = dialog->getTransparency();
465 int startFrame = dialog->getStartFrame();
466 int endFrame = dialog->getEndFrame();
468 QString sCameraLayerName = dialog->getCameraLayerName();
469 LayerCamera* cameraLayer =
static_cast<LayerCamera*
>(mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA));
473 hideQuestionMark(progress);
477 Status st = mEditor->object()->exportFrames(startFrame, endFrame,
484 mEditor->layers()->currentLayer()->name(),
491 ErrorDialog errorDialog(
tr(
"Something went wrong"),
tr(
"Unable to export one or more images in the image sequence."), st.details().html(), mParent);
501Status ActionCommands::exportImage()
510 auto cameraLayers = mEditor->
object()->getLayersByType<
LayerCamera >();
513 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
516 auto currLayer = mEditor->layers()->currentLayer();
517 if (currLayer->type() == Layer::CAMERA)
519 QString strName = currLayer->name();
520 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
521 [strName](std::pair<QString, QSize> p)
523 return p.first == strName;
526 Q_ASSERT(it != camerasInfo.end());
527 std::swap(camerasInfo[0], *it);
529 dialog->setCamerasInfo(camerasInfo);
538 QString filePath = dialog->getFilePath();
539 QSize exportSize = dialog->getExportSize();
540 QString exportFormat = dialog->getExportFormat();
541 bool useTransparency = dialog->getTransparency();
544 QString formatStr = exportFormat;
545 if (formatStr ==
"PNG" || formatStr ==
"png")
547 exportFormat =
"PNG";
550 if (formatStr ==
"JPG" || formatStr ==
"jpg" || formatStr ==
"JPEG" || formatStr ==
"jpeg")
552 exportFormat =
"JPG";
554 useTransparency =
false;
556 if (formatStr ==
"TIFF" || formatStr ==
"tiff" || formatStr ==
"TIF" || formatStr ==
"tif")
558 exportFormat =
"TIFF";
561 if (formatStr ==
"BMP" || formatStr ==
"bmp")
563 exportFormat =
"BMP";
565 useTransparency =
false;
567 if (formatStr ==
"WEBP" || formatStr ==
"webp") {
568 exportFormat =
"WEBP";
573 filePath += extension;
577 QString sCameraLayerName = dialog->getCameraLayerName();
578 LayerCamera* cameraLayer =
static_cast<LayerCamera*
>(mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA));
580 QTransform view = cameraLayer->getViewAtFrame(mEditor->currentFrame());
582 Status st = mEditor->object()->exportIm(mEditor->currentFrame(),
584 cameraLayer->getViewSize(),
593 ErrorDialog errorDialog(
tr(
"Something went wrong"),
tr(
"Unable to export image."), st.details().html(), mParent);
600void ActionCommands::flipSelectionX()
602 bool flipVertical =
false;
603 mEditor->flipSelection(flipVertical);
606void ActionCommands::flipSelectionY()
608 bool flipVertical =
true;
609 mEditor->flipSelection(flipVertical);
612void ActionCommands::selectAll()
614 mEditor->selectAll();
617void ActionCommands::deselectAll()
619 mEditor->deselectAll();
622void ActionCommands::ZoomIn()
624 mEditor->view()->scaleUp();
627void ActionCommands::ZoomOut()
629 mEditor->view()->scaleDown();
632void ActionCommands::rotateClockwise()
635 const float delta = mEditor->view()->isFlipHorizontal() == !mEditor->view()->isFlipVertical() ? -15.f : 15.f;
636 mEditor->view()->rotateRelative(delta);
639void ActionCommands::rotateCounterClockwise()
642 const float delta = mEditor->view()->isFlipHorizontal() == !mEditor->view()->isFlipVertical() ? 15.f : -15.f;
643 mEditor->view()->rotateRelative(delta);
646void ActionCommands::PlayStop()
649 if (playback->isPlaying())
659void ActionCommands::GotoNextFrame()
661 mEditor->scrubForward();
664void ActionCommands::GotoPrevFrame()
666 mEditor->scrubBackward();
669void ActionCommands::GotoNextKeyFrame()
671 mEditor->scrubNextKeyFrame();
674void ActionCommands::GotoPrevKeyFrame()
676 mEditor->scrubPreviousKeyFrame();
679Status ActionCommands::addNewKey()
682 if (mEditor->layers()->currentLayer()->type() == Layer::SOUND)
684 return importSound(FileType::SOUND);
691 mEditor->view()->forceUpdateViewTransform();
697void ActionCommands::exposeSelectedFrames(
int offset)
699 Layer* currentLayer = mEditor->layers()->currentLayer();
701 bool hasSelectedFrames = currentLayer->hasAnySelectedFrames();
705 KeyFrame* key = currentLayer->getLastKeyFrameAtPosition(mEditor->currentFrame());
706 if (!hasSelectedFrames) {
708 if (key ==
nullptr) {
return; }
709 currentLayer->setFrameSelected(key->pos(),
true);
713 emit mEditor->updateTimeLine();
718 if (!hasSelectedFrames) {
719 currentLayer->setFrameSelected(key->pos(),
false);
723void ActionCommands::addExposureToSelectedFrames()
725 exposeSelectedFrames(1);
728void ActionCommands::subtractExposureFromSelectedFrames()
730 exposeSelectedFrames(-1);
735 Layer* currentLayer = mEditor->layers()->currentLayer();
736 int currentPosition = mEditor->currentFrame();
742void ActionCommands::removeSelectedFrames()
744 Layer* currentLayer = mEditor->layers()->currentLayer();
746 if (!currentLayer->hasAnySelectedFrames()) {
return; }
749 tr(
"Remove selected frames",
"Windows title of remove selected frames pop-up."),
750 tr(
"Are you sure you want to remove the selected frames? This action is irreversible currently!"),
760 currentLayer->removeKeyFrame(pos);
762 mEditor->layers()->notifyLayerChanged(currentLayer);
765void ActionCommands::reverseSelectedFrames()
767 Layer* currentLayer = mEditor->layers()->currentLayer();
773 if (currentLayer->type() == Layer::CAMERA) {
774 mEditor->view()->forceUpdateViewTransform();
779void ActionCommands::removeKey()
781 mEditor->removeKey();
784void ActionCommands::duplicateLayer()
787 Layer* fromLayer = layerMgr->currentLayer();
788 int currFrame = mEditor->currentFrame();
790 Layer* toLayer = layerMgr->
createLayer(fromLayer->type(),
tr(
"%1 (copy)",
"Default duplicate layer name").arg(fromLayer->name()));
791 fromLayer->foreachKeyFrame([&] (
KeyFrame* key) {
793 toLayer->addOrReplaceKeyFrame(key->pos(), key);
794 if (toLayer->type() == Layer::SOUND)
796 mEditor->sound()->processSound(static_cast<SoundClip*>(key));
799 if (!fromLayer->keyExists(1)) {
800 toLayer->removeKeyFrame(1);
802 mEditor->scrubTo(currFrame);
805void ActionCommands::duplicateKey()
807 Layer* layer = mEditor->layers()->currentLayer();
808 if (layer ==
nullptr)
return;
809 if (!layer->visible())
811 mEditor->getScribbleArea()->showLayerNotVisibleWarning();
815 KeyFrame* key = layer->getKeyFrameAt(mEditor->currentFrame());
816 if (key ==
nullptr)
return;
824 int nextEmptyFrame = mEditor->currentFrame() + 1;
825 while (layer->keyExistsWhichCovers(nextEmptyFrame))
831 mEditor->scrubTo(nextEmptyFrame);
834 if (layer->type() == Layer::SOUND)
836 mEditor->sound()->processSound(
dynamic_cast<SoundClip*
>(dupKey));
837 showSoundClipWarningIfNeeded();
841 emit mEditor->layers()->currentLayerChanged(mEditor->layers()->currentLayerIndex());
844void ActionCommands::moveFrameForward()
846 Layer* layer = mEditor->layers()->currentLayer();
849 if (layer->moveKeyFrame(mEditor->currentFrame(), 1))
851 mEditor->scrubForward();
858void ActionCommands::moveFrameBackward()
860 Layer* layer = mEditor->layers()->currentLayer();
863 if (layer->moveKeyFrame(mEditor->currentFrame(), -1))
865 mEditor->scrubBackward();
871Status ActionCommands::addNewBitmapLayer()
876 mEditor->layers()->nameSuggestLayer(
tr(
"Bitmap Layer")), &ok);
879 mEditor->layers()->createBitmapLayer(text);
884Status ActionCommands::addNewVectorLayer()
889 mEditor->layers()->nameSuggestLayer(
tr(
"Vector Layer")), &ok);
892 mEditor->layers()->createVectorLayer(text);
897Status ActionCommands::addNewCameraLayer()
902 mEditor->layers()->nameSuggestLayer(
tr(
"Camera Layer")), &ok);
905 mEditor->layers()->createCameraLayer(text);
910Status ActionCommands::addNewSoundLayer()
915 mEditor->layers()->nameSuggestLayer(
tr(
"Sound Layer")), &ok);
916 if (ok && !strLayerName.
isEmpty())
918 Layer* layer = mEditor->layers()->createSoundLayer(strLayerName);
919 mEditor->layers()->setCurrentLayer(layer);
924Status ActionCommands::deleteCurrentLayer()
927 QString strLayerName = layerMgr->currentLayer()->name();
929 if (!layerMgr->canDeleteLayer(mEditor->currentLayerIndex())) {
930 return Status::CANCELED;
934 tr(
"Delete Layer",
"Windows title of Delete current layer pop-up."),
935 tr(
"Are you sure you want to delete layer: %1? This cannot be undone.").arg(strLayerName),
940 Status st = layerMgr->deleteLayer(mEditor->currentLayerIndex());
941 if (st == Status::ERROR_NEED_AT_LEAST_ONE_CAMERA_LAYER)
944 tr(
"Please keep at least one camera layer in project",
"text when failed to delete camera layer"));
950void ActionCommands::setLayerVisibilityIndex(
int index)
955void ActionCommands::changeKeyframeLineColor()
957 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP &&
958 mEditor->layers()->currentLayer()->keyExists(mEditor->currentFrame()))
962 layer->getBitmapImageAtFrame(mEditor->currentFrame())->fillNonAlphaPixels(color);
967void ActionCommands::changeallKeyframeLineColor()
969 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP)
973 for (
int i = layer->firstKeyFramePosition(); i <= layer->getMaxKeyFramePosition(); i++)
975 if (layer->keyExists(i))
976 layer->getBitmapImageAtFrame(i)->fillNonAlphaPixels(color);
983void ActionCommands::resetAllTools()
985 mEditor->tools()->resetAllTools();
988void ActionCommands::help()
990 QString url =
"http://www.pencil2d.org/doc/";
994void ActionCommands::quickGuide()
999 QFile quickGuideFile(
":/app/pencil2d_quick_guide.pdf");
1000 quickGuideFile.copy(sCopyDest);
1005void ActionCommands::website()
1007 QString url =
"https://www.pencil2d.org/";
1011void ActionCommands::forum()
1013 QString url =
"https://discuss.pencil2d.org/";
1017void ActionCommands::discord()
1019 QString url =
"https://discord.gg/8FxdV2g";
1023void ActionCommands::reportbug()
1025 QString url =
"https://github.com/pencil2d/pencil/issues";
1029void ActionCommands::checkForUpdates()
1032 dialog.startChecking();
1037void ActionCommands::openTemporaryDirectory()
1046void ActionCommands::about()
1054void ActionCommands::showSoundClipWarningIfNeeded()
1056 int clipCount = mEditor->sound()->soundClipCount();
1057 if (clipCount >= MovieExporter::MAX_SOUND_FRAMES && !mSuppressSoundWarning) {
1058 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));
1059 mSuppressSoundWarning =
true;
1061 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 endsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)