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 formatStr = exportFormat;
540 if (formatStr ==
"PNG" || formatStr ==
"png")
542 exportFormat =
"PNG";
545 if (formatStr ==
"JPG" || formatStr ==
"jpg" || formatStr ==
"JPEG" || formatStr ==
"jpeg")
547 exportFormat =
"JPG";
549 useTranparency =
false;
551 if (formatStr ==
"TIFF" || formatStr ==
"tiff" || formatStr ==
"TIF" || formatStr ==
"tif")
553 exportFormat =
"TIFF";
556 if (formatStr ==
"BMP" || formatStr ==
"bmp")
558 exportFormat =
"BMP";
560 useTranparency =
false;
562 if (formatStr ==
"WEBP" || formatStr ==
"webp") {
563 exportFormat =
"WEBP";
568 filePath += extension;
572 QString sCameraLayerName = dialog->getCameraLayerName();
573 LayerCamera* cameraLayer =
static_cast<LayerCamera*
>(mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA));
575 QTransform view = cameraLayer->getViewAtFrame(mEditor->currentFrame());
577 bool bOK = mEditor->object()->exportIm(mEditor->currentFrame(),
579 cameraLayer->getViewSize(),
590 tr(
"Unable to export image."),
597void ActionCommands::flipSelectionX()
599 bool flipVertical =
false;
600 mEditor->flipSelection(flipVertical);
603void ActionCommands::flipSelectionY()
605 bool flipVertical =
true;
606 mEditor->flipSelection(flipVertical);
609void ActionCommands::selectAll()
611 mEditor->selectAll();
614void ActionCommands::deselectAll()
616 mEditor->deselectAll();
619void ActionCommands::ZoomIn()
621 mEditor->view()->scaleUp();
624void ActionCommands::ZoomOut()
626 mEditor->view()->scaleDown();
629void ActionCommands::rotateClockwise()
632 const float delta = mEditor->view()->isFlipHorizontal() == !mEditor->view()->isFlipVertical() ? -15.f : 15.f;
633 mEditor->view()->rotateRelative(delta);
636void ActionCommands::rotateCounterClockwise()
639 const float delta = mEditor->view()->isFlipHorizontal() == !mEditor->view()->isFlipVertical() ? 15.f : -15.f;
640 mEditor->view()->rotateRelative(delta);
643void ActionCommands::PlayStop()
646 if (playback->isPlaying())
656void ActionCommands::GotoNextFrame()
658 mEditor->scrubForward();
661void ActionCommands::GotoPrevFrame()
663 mEditor->scrubBackward();
666void ActionCommands::GotoNextKeyFrame()
668 mEditor->scrubNextKeyFrame();
671void ActionCommands::GotoPrevKeyFrame()
673 mEditor->scrubPreviousKeyFrame();
676Status ActionCommands::addNewKey()
679 if (mEditor->layers()->currentLayer()->type() == Layer::SOUND)
681 return importSound(FileType::SOUND);
688 mEditor->view()->forceUpdateViewTransform();
694void ActionCommands::exposeSelectedFrames(
int offset)
696 Layer* currentLayer = mEditor->layers()->currentLayer();
698 bool hasSelectedFrames = currentLayer->hasAnySelectedFrames();
702 KeyFrame* key = currentLayer->getLastKeyFrameAtPosition(mEditor->currentFrame());
703 if (!hasSelectedFrames) {
705 if (key ==
nullptr) {
return; }
706 currentLayer->setFrameSelected(key->pos(),
true);
710 emit mEditor->updateTimeLine();
715 if (!hasSelectedFrames) {
716 currentLayer->setFrameSelected(key->pos(),
false);
720void ActionCommands::addExposureToSelectedFrames()
722 exposeSelectedFrames(1);
725void ActionCommands::subtractExposureFromSelectedFrames()
727 exposeSelectedFrames(-1);
732 Layer* currentLayer = mEditor->layers()->currentLayer();
733 int currentPosition = mEditor->currentFrame();
739void ActionCommands::removeSelectedFrames()
741 Layer* currentLayer = mEditor->layers()->currentLayer();
743 if (!currentLayer->hasAnySelectedFrames()) {
return; }
746 tr(
"Remove selected frames",
"Windows title of remove selected frames pop-up."),
747 tr(
"Are you sure you want to remove the selected frames? This action is irreversible currently!"),
757 currentLayer->removeKeyFrame(pos);
759 mEditor->layers()->notifyLayerChanged(currentLayer);
762void ActionCommands::reverseSelectedFrames()
764 Layer* currentLayer = mEditor->layers()->currentLayer();
770 if (currentLayer->type() == Layer::CAMERA) {
771 mEditor->view()->forceUpdateViewTransform();
776void ActionCommands::removeKey()
778 mEditor->removeKey();
781void ActionCommands::duplicateLayer()
784 Layer* fromLayer = layerMgr->currentLayer();
785 int currFrame = mEditor->currentFrame();
787 Layer* toLayer = layerMgr->
createLayer(fromLayer->type(),
tr(
"%1 (copy)",
"Default duplicate layer name").arg(fromLayer->name()));
788 fromLayer->foreachKeyFrame([&] (
KeyFrame* key) {
790 toLayer->addOrReplaceKeyFrame(key->pos(), key);
791 if (toLayer->type() == Layer::SOUND)
793 mEditor->sound()->processSound(static_cast<SoundClip*>(key));
796 if (!fromLayer->keyExists(1)) {
797 toLayer->removeKeyFrame(1);
799 mEditor->scrubTo(currFrame);
802void ActionCommands::duplicateKey()
804 Layer* layer = mEditor->layers()->currentLayer();
805 if (layer ==
nullptr)
return;
806 if (!layer->visible())
808 mEditor->getScribbleArea()->showLayerNotVisibleWarning();
812 KeyFrame* key = layer->getKeyFrameAt(mEditor->currentFrame());
813 if (key ==
nullptr)
return;
821 int nextEmptyFrame = mEditor->currentFrame() + 1;
822 while (layer->keyExistsWhichCovers(nextEmptyFrame))
828 mEditor->scrubTo(nextEmptyFrame);
831 if (layer->type() == Layer::SOUND)
833 mEditor->sound()->processSound(
dynamic_cast<SoundClip*
>(dupKey));
834 showSoundClipWarningIfNeeded();
838 emit mEditor->layers()->currentLayerChanged(mEditor->layers()->currentLayerIndex());
841void ActionCommands::moveFrameForward()
843 Layer* layer = mEditor->layers()->currentLayer();
846 if (layer->moveKeyFrame(mEditor->currentFrame(), 1))
848 mEditor->scrubForward();
855void ActionCommands::moveFrameBackward()
857 Layer* layer = mEditor->layers()->currentLayer();
860 if (layer->moveKeyFrame(mEditor->currentFrame(), -1))
862 mEditor->scrubBackward();
868Status ActionCommands::addNewBitmapLayer()
873 mEditor->layers()->nameSuggestLayer(
tr(
"Bitmap Layer")), &ok);
876 mEditor->layers()->createBitmapLayer(text);
881Status ActionCommands::addNewVectorLayer()
886 mEditor->layers()->nameSuggestLayer(
tr(
"Vector Layer")), &ok);
889 mEditor->layers()->createVectorLayer(text);
894Status ActionCommands::addNewCameraLayer()
899 mEditor->layers()->nameSuggestLayer(
tr(
"Camera Layer")), &ok);
902 mEditor->layers()->createCameraLayer(text);
907Status ActionCommands::addNewSoundLayer()
912 mEditor->layers()->nameSuggestLayer(
tr(
"Sound Layer")), &ok);
913 if (ok && !strLayerName.
isEmpty())
915 Layer* layer = mEditor->layers()->createSoundLayer(strLayerName);
916 mEditor->layers()->setCurrentLayer(layer);
921Status ActionCommands::deleteCurrentLayer()
924 QString strLayerName = layerMgr->currentLayer()->name();
926 if (!layerMgr->canDeleteLayer(mEditor->currentLayerIndex())) {
927 return Status::CANCELED;
931 tr(
"Delete Layer",
"Windows title of Delete current layer pop-up."),
932 tr(
"Are you sure you want to delete layer: %1? This cannot be undone.").arg(strLayerName),
937 Status st = layerMgr->deleteLayer(mEditor->currentLayerIndex());
938 if (st == Status::ERROR_NEED_AT_LEAST_ONE_CAMERA_LAYER)
941 tr(
"Please keep at least one camera layer in project",
"text when failed to delete camera layer"));
947void ActionCommands::setLayerVisibilityIndex(
int index)
952void ActionCommands::changeKeyframeLineColor()
954 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP &&
955 mEditor->layers()->currentLayer()->keyExists(mEditor->currentFrame()))
959 layer->getBitmapImageAtFrame(mEditor->currentFrame())->fillNonAlphaPixels(color);
964void ActionCommands::changeallKeyframeLineColor()
966 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP)
970 for (
int i = layer->firstKeyFramePosition(); i <= layer->getMaxKeyFramePosition(); i++)
972 if (layer->keyExists(i))
973 layer->getBitmapImageAtFrame(i)->fillNonAlphaPixels(color);
980void ActionCommands::resetAllTools()
982 mEditor->tools()->resetAllTools();
985void ActionCommands::help()
987 QString url =
"http://www.pencil2d.org/doc/";
991void ActionCommands::quickGuide()
996 QFile quickGuideFile(
":/app/pencil2d_quick_guide.pdf");
997 quickGuideFile.copy(sCopyDest);
1002void ActionCommands::website()
1004 QString url =
"https://www.pencil2d.org/";
1008void ActionCommands::forum()
1010 QString url =
"https://discuss.pencil2d.org/";
1014void ActionCommands::discord()
1016 QString url =
"https://discord.gg/8FxdV2g";
1020void ActionCommands::reportbug()
1022 QString url =
"https://github.com/pencil2d/pencil/issues";
1026void ActionCommands::checkForUpdates()
1029 dialog.startChecking();
1034void ActionCommands::openTemporaryDirectory()
1043void ActionCommands::about()
1051void ActionCommands::showSoundClipWarningIfNeeded()
1053 int clipCount = mEditor->sound()->soundClipCount();
1054 if (clipCount >= MovieExporter::MAX_SOUND_FRAMES && !mSuppressSoundWarning) {
1055 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));
1056 mSuppressSoundWarning =
true;
1058 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)