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 "movieimporter.h"
51#include "movieexporter.h"
52#include "filedialog.h"
53#include "exportmoviedialog.h"
54#include "exportimagedialog.h"
55#include "aboutdialog.h"
56#include "doubleprogressdialog.h"
57#include "checkupdatesdialog.h"
58#include "errordialog.h"
66ActionCommands::~ActionCommands() {}
68Status ActionCommands::importMovieVideo()
78 hideQuestionMark(progressDialog);
80 progressDialog.setMinimumWidth(250);
81 progressDialog.show();
85 information.setText(
tr(
"You are importing a lot of frames, beware this could take some time. Are you sure you want to proceed?"));
90 importer.setCore(mEditor);
94 Status st = importer.run(filePath, mEditor->playback()->fps(), FileType::MOVIE, [&progressDialog](
int prog) {
95 progressDialog.setValue(prog);
96 QApplication::processEvents();
97 }, [&progressDialog](
QString progMessage) {
98 progressDialog.setLabelText(progMessage);
101 int ret = information.exec();
102 return ret == QMessageBox::Yes;
105 if (!st.ok() && st != Status::CANCELED)
107 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
114 progressDialog.setValue(100);
115 progressDialog.close();
120Status ActionCommands::importSound(FileType type)
122 Layer* layer = mEditor->layers()->currentLayer();
123 if (layer ==
nullptr)
129 if (layer->type() != Layer::SOUND)
132 msg.
setText(
tr(
"No sound layer exists as a destination for your import. Create a new sound layer?"));
136 int buttonClicked = msg.
exec();
146 mEditor->layers()->nameSuggestLayer(
tr(
"Sound Layer",
"Default name on creating a sound layer")), &ok);
147 if (ok && !strLayerName.
isEmpty())
149 Layer* newLayer = mEditor->layers()->createSoundLayer(strLayerName);
150 mEditor->layers()->setCurrentLayer(newLayer);
158 layer = mEditor->layers()->currentLayer();
159 Q_ASSERT(layer->type() == Layer::SOUND);
177 st = Status::CANCELED;
183 st = convertSoundToWav(strSoundFile);
188 mEditor->removeKey();
189 emit mEditor->layers()->currentLayerChanged(mEditor->layers()->currentLayerIndex());
191 showSoundClipWarningIfNeeded();
197Status ActionCommands::convertSoundToWav(
const QString& filePath)
200 hideQuestionMark(progressDialog);
202 progressDialog.show();
205 importer.setCore(mEditor);
207 Status st = importer.run(filePath, mEditor->playback()->fps(), FileType::SOUND, [&progressDialog](
int prog) {
208 progressDialog.setValue(prog);
209 QApplication::processEvents();
210 }, [](
QString progressMessage) {
211 Q_UNUSED(progressMessage)
219 if (!st.ok() && st != Status::CANCELED)
221 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
227Status ActionCommands::exportGif()
230 return exportMovie(
true);
233Status ActionCommands::exportMovie(
bool isGif)
235 FileType fileType = (isGif) ? FileType::GIF : FileType::MOVIE;
237 int clipCount = mEditor->sound()->soundClipCount();
238 if (fileType == FileType::MOVIE && clipCount >= MovieExporter::MAX_SOUND_FRAMES)
240 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);
250 std::vector< std::pair<QString, QSize> > camerasInfo;
251 auto cameraLayers = mEditor->object()->getLayersByType<
LayerCamera >();
254 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
257 auto currLayer = mEditor->layers()->currentLayer();
258 if (currLayer->type() == Layer::CAMERA)
260 QString strName = currLayer->name();
261 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
262 [strName](std::pair<QString, QSize> p)
264 return p.first == strName;
267 Q_ASSERT(it != camerasInfo.end());
269 std::swap(camerasInfo[0], *it);
272 dialog->setCamerasInfo(camerasInfo);
277 dialog->setDefaultRange(1, length, lengthWithSounds);
284 QString strMoviePath = dialog->getFilePath();
287 desc.strFileName = strMoviePath;
288 desc.startFrame = dialog->getStartFrame();
289 desc.endFrame = dialog->getEndFrame();
290 desc.fps = mEditor->playback()->fps();
291 desc.exportSize = dialog->getExportSize();
292 desc.strCameraName = dialog->getSelectedCameraName();
293 desc.loop = dialog->getLoop();
294 desc.alpha = dialog->getTransparency();
305 connect(&progressDlg, &DoubleProgressDialog::canceled, [&ex]
311 float minorStart, minorLength;
313 Status st = ex.
run(mEditor->object(), desc,
314 [&progressDlg, &minorStart, &minorLength](
float f,
float final)
316 progressDlg.major->setValue(f);
319 minorLength = qMax(0.f, final - minorStart);
321 QApplication::processEvents();
323 [&progressDlg, &minorStart, &minorLength](
float f) {
324 progressDlg.minor->setValue(f);
326 progressDlg.major->setValue(minorStart + f * minorLength);
328 QApplication::processEvents();
331 progressDlg.setStatus(s);
332 QApplication::processEvents();
342 tr(
"Finished. Open file location?"));
346 QString path = dialog->getAbsolutePath();
352 tr(
"Finished. Open movie now?",
"When movie export done."));
360 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);
364 else if(st != Status::CANCELED)
366 ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
373Status ActionCommands::exportImageSequence()
380 std::vector< std::pair<QString, QSize> > camerasInfo;
381 auto cameraLayers = mEditor->object()->getLayersByType<
LayerCamera >();
384 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
387 auto currLayer = mEditor->layers()->currentLayer();
388 if (currLayer->type() == Layer::CAMERA)
390 QString strName = currLayer->name();
391 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
392 [strName](std::pair<QString, QSize> p)
394 return p.first == strName;
397 Q_ASSERT(it != camerasInfo.end());
398 std::swap(camerasInfo[0], *it);
400 dialog->setCamerasInfo(camerasInfo);
405 dialog->setDefaultRange(1, length, lengthWithSounds);
414 QString strFilePath = dialog->getFilePath();
415 QSize exportSize = dialog->getExportSize();
416 QString exportFormat = dialog->getExportFormat();
417 bool exportKeyframesOnly = dialog->getExportKeyframesOnly();
418 bool useTranparency = dialog->getTransparency();
419 int startFrame = dialog->getStartFrame();
420 int endFrame = dialog->getEndFrame();
422 QString sCameraLayerName = dialog->getCameraLayerName();
423 LayerCamera* cameraLayer =
static_cast<LayerCamera*
>(mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA));
427 hideQuestionMark(progress);
431 mEditor->object()->exportFrames(startFrame, endFrame,
438 mEditor->layers()->currentLayer()->name(),
448Status ActionCommands::exportImage()
457 auto cameraLayers = mEditor->
object()->getLayersByType<
LayerCamera >();
460 camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
463 auto currLayer = mEditor->layers()->currentLayer();
464 if (currLayer->type() == Layer::CAMERA)
466 QString strName = currLayer->name();
467 auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
468 [strName](std::pair<QString, QSize> p)
470 return p.first == strName;
473 Q_ASSERT(it != camerasInfo.end());
474 std::swap(camerasInfo[0], *it);
476 dialog->setCamerasInfo(camerasInfo);
485 QString filePath = dialog->getFilePath();
486 QSize exportSize = dialog->getExportSize();
487 QString exportFormat = dialog->getExportFormat();
488 bool useTranparency = dialog->getTransparency();
491 QString sCameraLayerName = dialog->getCameraLayerName();
492 LayerCamera* cameraLayer =
static_cast<LayerCamera*
>(mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA));
494 QTransform view = cameraLayer->getViewAtFrame(mEditor->currentFrame());
496 bool bOK = mEditor->object()->exportIm(mEditor->currentFrame(),
498 cameraLayer->getViewSize(),
509 tr(
"Unable to export image."),
516void ActionCommands::flipSelectionX()
518 bool flipVertical =
false;
519 mEditor->flipSelection(flipVertical);
522void ActionCommands::flipSelectionY()
524 bool flipVertical =
true;
525 mEditor->flipSelection(flipVertical);
528void ActionCommands::selectAll()
530 mEditor->selectAll();
533void ActionCommands::deselectAll()
535 mEditor->deselectAll();
538void ActionCommands::ZoomIn()
540 mEditor->view()->scaleUp();
543void ActionCommands::ZoomOut()
545 mEditor->view()->scaleDown();
548void ActionCommands::rotateClockwise()
551 const float delta = mEditor->view()->isFlipHorizontal() == !mEditor->view()->isFlipVertical() ? -15.f : 15.f;
552 mEditor->view()->rotateRelative(delta);
555void ActionCommands::rotateCounterClockwise()
558 const float delta = mEditor->view()->isFlipHorizontal() == !mEditor->view()->isFlipVertical() ? 15.f : -15.f;
559 mEditor->view()->rotateRelative(delta);
562void ActionCommands::PlayStop()
565 if (playback->isPlaying())
575void ActionCommands::GotoNextFrame()
577 mEditor->scrubForward();
580void ActionCommands::GotoPrevFrame()
582 mEditor->scrubBackward();
585void ActionCommands::GotoNextKeyFrame()
587 mEditor->scrubNextKeyFrame();
590void ActionCommands::GotoPrevKeyFrame()
592 mEditor->scrubPreviousKeyFrame();
595Status ActionCommands::addNewKey()
598 if (mEditor->layers()->currentLayer()->type() == Layer::SOUND)
600 return importSound(FileType::SOUND);
603 KeyFrame* key = mEditor->addNewKey();
607 mEditor->view()->forceUpdateViewTransform();
613void ActionCommands::exposeSelectedFrames(
int offset)
615 Layer* currentLayer = mEditor->layers()->currentLayer();
617 bool hasSelectedFrames = currentLayer->hasAnySelectedFrames();
621 KeyFrame* key = currentLayer->getLastKeyFrameAtPosition(mEditor->currentFrame());
622 if (!hasSelectedFrames) {
624 if (key ==
nullptr) {
return; }
625 currentLayer->setFrameSelected(key->pos(),
true);
629 emit mEditor->updateTimeLine();
634 if (!hasSelectedFrames) {
635 currentLayer->setFrameSelected(key->pos(),
false);
639void ActionCommands::addExposureToSelectedFrames()
641 exposeSelectedFrames(1);
644void ActionCommands::subtractExposureFromSelectedFrames()
646 exposeSelectedFrames(-1);
651 Layer* currentLayer = mEditor->layers()->currentLayer();
652 int currentPosition = mEditor->currentFrame();
658void ActionCommands::removeSelectedFrames()
660 Layer* currentLayer = mEditor->layers()->currentLayer();
662 if (!currentLayer->hasAnySelectedFrames()) {
return; }
665 tr(
"Remove selected frames",
"Windows title of remove selected frames pop-up."),
666 tr(
"Are you sure you want to remove the selected frames? This action is irreversible currently!"),
676 currentLayer->removeKeyFrame(pos);
678 mEditor->layers()->notifyLayerChanged(currentLayer);
681void ActionCommands::reverseSelectedFrames()
683 Layer* currentLayer = mEditor->layers()->currentLayer();
689 if (currentLayer->type() == Layer::CAMERA) {
690 mEditor->view()->forceUpdateViewTransform();
695void ActionCommands::removeKey()
697 mEditor->removeKey();
700void ActionCommands::duplicateLayer()
703 Layer* fromLayer = layerMgr->currentLayer();
704 int currFrame = mEditor->currentFrame();
706 Layer* toLayer = layerMgr->
createLayer(fromLayer->type(),
tr(
"%1 (copy)",
"Default duplicate layer name").arg(fromLayer->name()));
707 fromLayer->foreachKeyFrame([&] (
KeyFrame* key) {
709 toLayer->addOrReplaceKeyFrame(key->pos(), key);
710 if (toLayer->type() == Layer::SOUND)
712 mEditor->sound()->processSound(static_cast<SoundClip*>(key));
719 if (!fromLayer->keyExists(1)) {
720 toLayer->removeKeyFrame(1);
722 mEditor->scrubTo(currFrame);
725void ActionCommands::duplicateKey()
727 Layer* layer = mEditor->layers()->currentLayer();
728 if (layer ==
nullptr)
return;
729 if (!layer->visible())
731 mEditor->getScribbleArea()->showLayerNotVisibleWarning();
735 KeyFrame* key = layer->getKeyFrameAt(mEditor->currentFrame());
736 if (key ==
nullptr)
return;
744 int nextEmptyFrame = mEditor->currentFrame() + 1;
745 while (layer->keyExistsWhichCovers(nextEmptyFrame))
750 layer->addKeyFrame(nextEmptyFrame, dupKey);
751 mEditor->scrubTo(nextEmptyFrame);
754 if (layer->type() == Layer::SOUND)
756 mEditor->sound()->processSound(
dynamic_cast<SoundClip*
>(dupKey));
757 showSoundClipWarningIfNeeded();
761 dupKey->setFileName(
"");
762 dupKey->modification();
766 emit mEditor->layers()->currentLayerChanged(mEditor->layers()->currentLayerIndex());
769void ActionCommands::moveFrameForward()
771 Layer* layer = mEditor->layers()->currentLayer();
774 if (layer->moveKeyFrame(mEditor->currentFrame(), 1))
776 mEditor->scrubForward();
783void ActionCommands::moveFrameBackward()
785 Layer* layer = mEditor->layers()->currentLayer();
788 if (layer->moveKeyFrame(mEditor->currentFrame(), -1))
790 mEditor->scrubBackward();
796Status ActionCommands::addNewBitmapLayer()
801 mEditor->layers()->nameSuggestLayer(
tr(
"Bitmap Layer")), &ok);
804 mEditor->layers()->createBitmapLayer(text);
809Status ActionCommands::addNewVectorLayer()
814 mEditor->layers()->nameSuggestLayer(
tr(
"Vector Layer")), &ok);
817 mEditor->layers()->createVectorLayer(text);
822Status ActionCommands::addNewCameraLayer()
827 mEditor->layers()->nameSuggestLayer(
tr(
"Camera Layer")), &ok);
830 mEditor->layers()->createCameraLayer(text);
835Status ActionCommands::addNewSoundLayer()
840 mEditor->layers()->nameSuggestLayer(
tr(
"Sound Layer")), &ok);
841 if (ok && !strLayerName.
isEmpty())
843 Layer* layer = mEditor->layers()->createSoundLayer(strLayerName);
844 mEditor->layers()->setCurrentLayer(layer);
849Status ActionCommands::deleteCurrentLayer()
852 QString strLayerName = layerMgr->currentLayer()->name();
854 if (!layerMgr->canDeleteLayer(mEditor->currentLayerIndex())) {
855 return Status::CANCELED;
859 tr(
"Delete Layer",
"Windows title of Delete current layer pop-up."),
860 tr(
"Are you sure you want to delete layer: %1? This cannot be undone.").arg(strLayerName),
865 Status st = layerMgr->deleteLayer(mEditor->currentLayerIndex());
866 if (st == Status::ERROR_NEED_AT_LEAST_ONE_CAMERA_LAYER)
869 tr(
"Please keep at least one camera layer in project",
"text when failed to delete camera layer"));
875void ActionCommands::setLayerVisibilityIndex(
int index)
880void ActionCommands::changeKeyframeLineColor()
882 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP &&
883 mEditor->layers()->currentLayer()->keyExists(mEditor->currentFrame()))
887 layer->getBitmapImageAtFrame(mEditor->currentFrame())->fillNonAlphaPixels(color);
892void ActionCommands::changeallKeyframeLineColor()
894 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP)
898 for (
int i = layer->firstKeyFramePosition(); i <= layer->getMaxKeyFramePosition(); i++)
900 if (layer->keyExists(i))
901 layer->getBitmapImageAtFrame(i)->fillNonAlphaPixels(color);
907void ActionCommands::help()
909 QString url =
"http://www.pencil2d.org/doc/";
913void ActionCommands::quickGuide()
918 QFile quickGuideFile(
":/app/pencil2d_quick_guide.pdf");
919 quickGuideFile.copy(sCopyDest);
924void ActionCommands::website()
926 QString url =
"https://www.pencil2d.org/";
930void ActionCommands::forum()
932 QString url =
"https://discuss.pencil2d.org/";
936void ActionCommands::discord()
938 QString url =
"https://discord.gg/8FxdV2g";
942void ActionCommands::reportbug()
944 QString url =
"https://github.com/pencil2d/pencil/issues";
948void ActionCommands::checkForUpdates()
951 dialog.startChecking();
956void ActionCommands::openTemporaryDirectory()
965void ActionCommands::about()
973void ActionCommands::showSoundClipWarningIfNeeded()
975 int clipCount = mEditor->sound()->soundClipCount();
976 if (clipCount >= MovieExporter::MAX_SOUND_FRAMES && !mSuppressSoundWarning) {
977 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));
978 mSuppressSoundWarning =
true;
980 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 updateFrame(int frameNumber)
Will call update() and update the canvas Only call this directly If you need the cache to be intact a...
void framesModified()
This should be emitted after modifying multiple frames.
void frameModified(int frameNumber)
This should be emitted after modifying the frame content.
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.
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.
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)