All Classes Namespaces Functions Variables Enumerations Properties Pages
actioncommands.cpp
1 /*
2 
3 Pencil2D - Traditional Animation Software
4 Copyright (C) 2012-2020 Matthew Chiawen Chang
5 
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; version 2 of the License.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14 
15 */
16 
17 #include "actioncommands.h"
18 
19 #include <QInputDialog>
20 #include <QMessageBox>
21 #include <QProgressDialog>
22 #include <QApplication>
23 #include <QDesktopServices>
24 #include <QStandardPaths>
25 #include <QFileDialog>
26 
27 #include "pencildef.h"
28 #include "editor.h"
29 #include "object.h"
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"
38 #include "util.h"
39 #include "app_util.h"
40 
41 #include "layercamera.h"
42 #include "layersound.h"
43 #include "layerbitmap.h"
44 #include "layervector.h"
45 #include "bitmapimage.h"
46 #include "vectorimage.h"
47 #include "soundclip.h"
48 #include "camera.h"
49 
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"
59 
60 
61 ActionCommands::ActionCommands(QWidget* parent) : QObject(parent)
62 {
63  mParent = parent;
64 }
65 
66 ActionCommands::~ActionCommands() {}
67 
68 Status ActionCommands::importMovieVideo()
69 {
70  QString filePath = FileDialog::getOpenFileName(mParent, FileType::MOVIE);
71  if (filePath.isEmpty())
72  {
73  return Status::FAIL;
74  }
75 
76  // Show a progress dialog, as this can take a while if you have lots of images.
77  QProgressDialog progressDialog(tr("Importing movie..."), tr("Abort"), 0, 100, mParent);
78  hideQuestionMark(progressDialog);
79  progressDialog.setWindowModality(Qt::WindowModal);
80  progressDialog.setMinimumWidth(250);
81  progressDialog.show();
82 
83  QMessageBox information(mParent);
84  information.setIcon(QMessageBox::Warning);
85  information.setText(tr("You are importing a lot of frames, beware this could take some time. Are you sure you want to proceed?"));
86  information.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
87  information.setDefaultButton(QMessageBox::Yes);
88 
89  MovieImporter importer(this);
90  importer.setCore(mEditor);
91 
92  connect(&progressDialog, &QProgressDialog::canceled, &importer, &MovieImporter::cancel);
93 
94  Status st = importer.run(filePath, mEditor->playback()->fps(), FileType::MOVIE, [&progressDialog](int prog) {
95  progressDialog.setValue(prog);
97  }, [&progressDialog](QString progMessage) {
98  progressDialog.setLabelText(progMessage);
99  }, [&information]() {
100 
101  int ret = information.exec();
102  return ret == QMessageBox::Yes;
103  });
104 
105  if (!st.ok() && st != Status::CANCELED)
106  {
107  ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
108  errorDialog.exec();
109  }
110 
111  mEditor->layers()->notifyAnimationLengthChanged();
112 
113  progressDialog.setValue(100);
114  progressDialog.close();
115 
116  return Status::OK;
117 }
118 
119 Status ActionCommands::importMovieAudio()
120 {
121  QString filePath = FileDialog::getOpenFileName(mParent, FileType::MOVIE);
122  if (filePath.isEmpty())
123  {
124  return Status::FAIL;
125  }
126 
127  // Show a progress dialog, as this can take a while if you have lots of images.
128  QProgressDialog progressDialog(tr("Importing movie audio..."), tr("Abort"), 0, 100, mParent);
129  hideQuestionMark(progressDialog);
130  progressDialog.setWindowModality(Qt::WindowModal);
131  progressDialog.show();
132 
133  MovieImporter importer(this);
134  importer.setCore(mEditor);
135 
136  connect(&progressDialog, &QProgressDialog::canceled, &importer, &MovieImporter::cancel);
137 
138  Status st = importer.run(filePath, mEditor->playback()->fps(), FileType::SOUND, [&progressDialog](int prog) {
139  progressDialog.setValue(prog);
141  }, [](QString progressMessage) {
142  Q_UNUSED(progressMessage)
143  // Not neeeded
144  }, []() {
145  return true;
146  });
147 
148  if (!st.ok() && st != Status::CANCELED)
149  {
150  ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
151  errorDialog.exec();
152  }
153 
154  mEditor->layers()->notifyAnimationLengthChanged();
155 
156  progressDialog.setValue(100);
157  progressDialog.close();
158 
159  return Status::OK;
160 }
161 
162 Status ActionCommands::importSound()
163 {
164  Layer* layer = mEditor->layers()->currentLayer();
165  if (layer == nullptr)
166  {
167  Q_ASSERT(layer);
168  return Status::FAIL;
169  }
170 
171  if (layer->type() != Layer::SOUND)
172  {
173  QMessageBox msg;
174  msg.setText(tr("No sound layer exists as a destination for your import. Create a new sound layer?"));
175  msg.addButton(tr("Create sound layer"), QMessageBox::AcceptRole);
176  msg.addButton(tr("Don't create layer"), QMessageBox::RejectRole);
177 
178  int buttonClicked = msg.exec();
179  if (buttonClicked != QMessageBox::AcceptRole)
180  {
181  return Status::SAFE;
182  }
183 
184  // Create new sound layer.
185  bool ok = false;
186  QString strLayerName = QInputDialog::getText(mParent, tr("Layer Properties", "Dialog title on creating a sound layer"),
187  tr("Layer name:"), QLineEdit::Normal,
188  mEditor->layers()->nameSuggestLayer(tr("Sound Layer", "Default name on creating a sound layer")), &ok);
189  if (ok && !strLayerName.isEmpty())
190  {
191  Layer* newLayer = mEditor->layers()->createSoundLayer(strLayerName);
192  mEditor->layers()->setCurrentLayer(newLayer);
193  }
194  else
195  {
196  return Status::SAFE;
197  }
198  }
199 
200  layer = mEditor->layers()->currentLayer();
201  Q_ASSERT(layer->type() == Layer::SOUND);
202 
203 
204  int currentFrame = mEditor->currentFrame();
205 
206  SoundClip* key = static_cast<SoundClip*>(mEditor->addNewKey());
207 
208  if (key == nullptr)
209  {
210  // Probably tried to modify a hidden layer or something like that
211  // Let Editor handle the warnings
212  return Status::SAFE;
213  }
214 
215  QString strSoundFile = FileDialog::getOpenFileName(mParent, FileType::SOUND);
216 
217  if (strSoundFile.isEmpty())
218  {
219  return Status::SAFE;
220  }
221 
222  Status st = Status::FAIL;
223 
224  if (strSoundFile.endsWith(".wav"))
225  {
226  st = mEditor->sound()->loadSound(key, strSoundFile);
227  }
228  else
229  {
230  st = convertSoundToWav(strSoundFile);
231  }
232 
233  if (!st.ok())
234  {
235  layer->removeKeyFrame(currentFrame);
236  }
237 
238  return st;
239 }
240 
241 Status ActionCommands::convertSoundToWav(const QString& filePath)
242 {
243  QProgressDialog progressDialog(tr("Importing sound..."), tr("Abort"), 0, 100, mParent);
244  hideQuestionMark(progressDialog);
245  progressDialog.setWindowModality(Qt::WindowModal);
246  progressDialog.show();
247 
248  MovieImporter importer(this);
249  importer.setCore(mEditor);
250 
251  Status st = importer.run(filePath, mEditor->playback()->fps(), FileType::SOUND, [&progressDialog](int prog) {
252  progressDialog.setValue(prog);
254  }, [](QString progressMessage) {
255  Q_UNUSED(progressMessage)
256  // Not needed
257  }, []() {
258  return true;
259  });
260 
261  connect(&progressDialog, &QProgressDialog::canceled, &importer, &MovieImporter::cancel);
262 
263  if (!st.ok() && st != Status::CANCELED)
264  {
265  ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
266  errorDialog.exec();
267  }
268  return st;
269 }
270 
271 Status ActionCommands::exportGif()
272 {
273  // exporting gif
274  return exportMovie(true);
275 }
276 
277 Status ActionCommands::exportMovie(bool isGif)
278 {
279  ExportMovieDialog* dialog = nullptr;
280  if (isGif) {
281  dialog = new ExportMovieDialog(mParent, ImportExportDialog::Export, FileType::GIF);
282  } else {
283  dialog = new ExportMovieDialog(mParent);
284  }
285  OnScopeExit(dialog->deleteLater());
286 
287  dialog->init();
288 
289  std::vector< std::pair<QString, QSize> > camerasInfo;
290  auto cameraLayers = mEditor->object()->getLayersByType< LayerCamera >();
291  for (LayerCamera* i : cameraLayers)
292  {
293  camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
294  }
295 
296  auto currLayer = mEditor->layers()->currentLayer();
297  if (currLayer->type() == Layer::CAMERA)
298  {
299  QString strName = currLayer->name();
300  auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
301  [strName](std::pair<QString, QSize> p)
302  {
303  return p.first == strName;
304  });
305 
306  Q_ASSERT(it != camerasInfo.end());
307 
308  std::swap(camerasInfo[0], *it);
309  }
310 
311  dialog->setCamerasInfo(camerasInfo);
312 
313  int lengthWithSounds = mEditor->layers()->animationLength(true);
314  int length = mEditor->layers()->animationLength(false);
315 
316  dialog->setDefaultRange(1, length, lengthWithSounds);
317  dialog->exec();
318 
319  if (dialog->result() == QDialog::Rejected)
320  {
321  return Status::SAFE;
322  }
323  QString strMoviePath = dialog->getFilePath();
324 
325  ExportMovieDesc desc;
326  desc.strFileName = strMoviePath;
327  desc.startFrame = dialog->getStartFrame();
328  desc.endFrame = dialog->getEndFrame();
329  desc.fps = mEditor->playback()->fps();
330  desc.exportSize = dialog->getExportSize();
331  desc.strCameraName = dialog->getSelectedCameraName();
332  desc.loop = dialog->getLoop();
333  desc.alpha = dialog->getTransparency();
334 
335  DoubleProgressDialog progressDlg;
336  progressDlg.setWindowModality(Qt::WindowModal);
337  progressDlg.setWindowTitle(tr("Exporting movie"));
339  progressDlg.setWindowFlags(eFlags);
340  progressDlg.show();
341 
342  MovieExporter ex;
343 
344  connect(&progressDlg, &DoubleProgressDialog::canceled, [&ex]
345  {
346  ex.cancel();
347  });
348 
349  // The start points and length for the current minor operation segment on the major progress bar
350  float minorStart, minorLength;
351 
352  Status st = ex.run(mEditor->object(), desc,
353  [&progressDlg, &minorStart, &minorLength](float f, float final)
354  {
355  progressDlg.major->setValue(f);
356 
357  minorStart = f;
358  minorLength = qMax(0.f, final - minorStart);
359 
361  },
362  [&progressDlg, &minorStart, &minorLength](float f) {
363  progressDlg.minor->setValue(f);
364 
365  progressDlg.major->setValue(minorStart + f * minorLength);
366 
368  },
369  [&progressDlg](QString s) {
370  progressDlg.setStatus(s);
372  }
373  );
374 
375  if (st.ok())
376  {
377  if (QFile::exists(strMoviePath))
378  {
379  if (isGif) {
380  auto btn = QMessageBox::question(mParent, "Pencil2D",
381  tr("Finished. Open file location?"));
382 
383  if (btn == QMessageBox::Yes)
384  {
385  QString path = dialog->getAbsolutePath();
387  }
388  return Status::OK;
389  }
390  auto btn = QMessageBox::question(mParent, "Pencil2D",
391  tr("Finished. Open movie now?", "When movie export done."));
392  if (btn == QMessageBox::Yes)
393  {
395  }
396  }
397  else
398  {
399  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);
400  errorDialog.exec();
401  }
402  }
403  else if(st != Status::CANCELED)
404  {
405  ErrorDialog errorDialog(st.title(), st.description(), st.details().html(), mParent);
406  errorDialog.exec();
407  }
408 
409  return st;
410 }
411 
412 Status ActionCommands::exportImageSequence()
413 {
414  auto dialog = new ExportImageDialog(mParent, FileType::IMAGE_SEQUENCE);
415  OnScopeExit(dialog->deleteLater());
416 
417  dialog->init();
418 
419  std::vector< std::pair<QString, QSize> > camerasInfo;
420  auto cameraLayers = mEditor->object()->getLayersByType< LayerCamera >();
421  for (LayerCamera* i : cameraLayers)
422  {
423  camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
424  }
425 
426  auto currLayer = mEditor->layers()->currentLayer();
427  if (currLayer->type() == Layer::CAMERA)
428  {
429  QString strName = currLayer->name();
430  auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
431  [strName](std::pair<QString, QSize> p)
432  {
433  return p.first == strName;
434  });
435 
436  Q_ASSERT(it != camerasInfo.end());
437  std::swap(camerasInfo[0], *it);
438  }
439  dialog->setCamerasInfo(camerasInfo);
440 
441  int lengthWithSounds = mEditor->layers()->animationLength(true);
442  int length = mEditor->layers()->animationLength(false);
443 
444  dialog->setDefaultRange(1, length, lengthWithSounds);
445 
446  dialog->exec();
447 
448  if (dialog->result() == QDialog::Rejected)
449  {
450  return Status::SAFE;
451  }
452 
453  QString strFilePath = dialog->getFilePath();
454  QSize exportSize = dialog->getExportSize();
455  QString exportFormat = dialog->getExportFormat();
456  bool exportKeyframesOnly = dialog->getExportKeyframesOnly();
457  bool useTranparency = dialog->getTransparency();
458  int startFrame = dialog->getStartFrame();
459  int endFrame = dialog->getEndFrame();
460 
461  QString sCameraLayerName = dialog->getCameraLayerName();
462  LayerCamera* cameraLayer = static_cast<LayerCamera*>(mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA));
463 
464  // Show a progress dialog, as this can take a while if you have lots of frames.
465  QProgressDialog progress(tr("Exporting image sequence..."), tr("Abort"), 0, 100, mParent);
466  hideQuestionMark(progress);
467  progress.setWindowModality(Qt::WindowModal);
468  progress.show();
469 
470  mEditor->object()->exportFrames(startFrame, endFrame,
471  cameraLayer,
472  exportSize,
473  strFilePath,
474  exportFormat,
475  useTranparency,
476  exportKeyframesOnly,
477  mEditor->layers()->currentLayer()->name(),
478  true,
479  &progress,
480  100);
481 
482  progress.close();
483 
484  return Status::OK;
485 }
486 
487 Status ActionCommands::exportImage()
488 {
489  // Options
490  auto dialog = new ExportImageDialog(mParent, FileType::IMAGE);
491  OnScopeExit(dialog->deleteLater());
492 
493  dialog->init();
494 
495  std::vector< std::pair<QString, QSize> > camerasInfo;
496  auto cameraLayers = mEditor->object()->getLayersByType< LayerCamera >();
497  for (LayerCamera* i : cameraLayers)
498  {
499  camerasInfo.push_back(std::make_pair(i->name(), i->getViewSize()));
500  }
501 
502  auto currLayer = mEditor->layers()->currentLayer();
503  if (currLayer->type() == Layer::CAMERA)
504  {
505  QString strName = currLayer->name();
506  auto it = std::find_if(camerasInfo.begin(), camerasInfo.end(),
507  [strName](std::pair<QString, QSize> p)
508  {
509  return p.first == strName;
510  });
511 
512  Q_ASSERT(it != camerasInfo.end());
513  std::swap(camerasInfo[0], *it);
514  }
515  dialog->setCamerasInfo(camerasInfo);
516 
517  dialog->exec();
518 
519  if (dialog->result() == QDialog::Rejected)
520  {
521  return Status::SAFE;
522  }
523 
524  QString filePath = dialog->getFilePath();
525  QSize exportSize = dialog->getExportSize();
526  QString exportFormat = dialog->getExportFormat();
527  bool useTranparency = dialog->getTransparency();
528 
529  // Export
530  QString sCameraLayerName = dialog->getCameraLayerName();
531  LayerCamera* cameraLayer = static_cast<LayerCamera*>(mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA));
532 
533  QTransform view = cameraLayer->getViewAtFrame(mEditor->currentFrame());
534 
535  bool bOK = mEditor->object()->exportIm(mEditor->currentFrame(),
536  view,
537  cameraLayer->getViewSize(),
538  exportSize,
539  filePath,
540  exportFormat,
541  true,
542  useTranparency);
543 
544  if (!bOK)
545  {
546  QMessageBox::warning(mParent,
547  tr("Warning"),
548  tr("Unable to export image."),
550  return Status::FAIL;
551  }
552  return Status::OK;
553 }
554 
555 void ActionCommands::flipSelectionX()
556 {
557  bool flipVertical = false;
558  mEditor->flipSelection(flipVertical);
559 }
560 
561 void ActionCommands::flipSelectionY()
562 {
563  bool flipVertical = true;
564  mEditor->flipSelection(flipVertical);
565 }
566 
567 void ActionCommands::selectAll()
568 {
569  mEditor->selectAll();
570 }
571 
572 void ActionCommands::deselectAll()
573 {
574  mEditor->deselectAll();
575 }
576 
577 void ActionCommands::ZoomIn()
578 {
579  mEditor->view()->scaleUp();
580 }
581 
582 void ActionCommands::ZoomOut()
583 {
584  mEditor->view()->scaleDown();
585 }
586 
587 void ActionCommands::rotateClockwise()
588 {
589  float currentRotation = mEditor->view()->rotation();
590  mEditor->view()->rotate(currentRotation + 15.f);
591 }
592 
593 void ActionCommands::rotateCounterClockwise()
594 {
595  float currentRotation = mEditor->view()->rotation();
596  mEditor->view()->rotate(currentRotation - 15.f);
597 }
598 
599 void ActionCommands::PlayStop()
600 {
601  PlaybackManager* playback = mEditor->playback();
602  if (playback->isPlaying())
603  {
604  playback->stop();
605  }
606  else
607  {
608  playback->play();
609  }
610 }
611 
612 void ActionCommands::GotoNextFrame()
613 {
614  mEditor->scrubForward();
615 }
616 
617 void ActionCommands::GotoPrevFrame()
618 {
619  mEditor->scrubBackward();
620 }
621 
622 void ActionCommands::GotoNextKeyFrame()
623 {
624  mEditor->scrubNextKeyFrame();
625 }
626 
627 void ActionCommands::GotoPrevKeyFrame()
628 {
629  mEditor->scrubPreviousKeyFrame();
630 }
631 
632 Status ActionCommands::addNewKey()
633 {
634  KeyFrame* key = mEditor->addNewKey();
635 
636  SoundClip* clip = dynamic_cast<SoundClip*>(key);
637  if (clip)
638  {
639  QString strSoundFile = FileDialog::getOpenFileName(mParent, FileType::SOUND);
640 
641  if (strSoundFile.isEmpty())
642  {
643  mEditor->layers()->currentLayer()->removeKeyFrame(clip->pos());
644  return Status::SAFE;
645  }
646  Status st = mEditor->sound()->loadSound(clip, strSoundFile);
647  if (!st.ok())
648  {
649  mEditor->layers()->currentLayer()->removeKeyFrame(clip->pos());
650  return Status::ERROR_LOAD_SOUND_FILE;
651  }
652  }
653 
654  Camera* cam = dynamic_cast<Camera*>(key);
655  if (cam)
656  {
657  mEditor->view()->updateViewTransforms();
658  }
659 
660  return Status::OK;
661 }
662 
663 void ActionCommands::removeKey()
664 {
665  mEditor->removeKey();
666 
667  Layer* layer = mEditor->layers()->currentLayer();
668  if (layer->keyFrameCount() == 0)
669  {
670  layer->addNewKeyFrameAt(1);
671  }
672 }
673 
674 void ActionCommands::duplicateKey()
675 {
676  Layer* layer = mEditor->layers()->currentLayer();
677  if (layer == nullptr) return;
678  if (!layer->visible())
679  {
680  mEditor->getScribbleArea()->showLayerNotVisibleWarning();
681  return;
682  }
683 
684  KeyFrame* key = layer->getKeyFrameAt(mEditor->currentFrame());
685  if (key == nullptr) return;
686 
687  KeyFrame* dupKey = key->clone();
688 
689  int nextEmptyFrame = mEditor->currentFrame() + 1;
690  while (layer->keyExistsWhichCovers(nextEmptyFrame))
691  {
692  nextEmptyFrame += 1;
693  }
694 
695  layer->addKeyFrame(nextEmptyFrame, dupKey);
696  mEditor->scrubTo(nextEmptyFrame);
697 
698  if (layer->type() == Layer::SOUND)
699  {
700  mEditor->sound()->processSound(dynamic_cast<SoundClip*>(dupKey));
701  }
702  else
703  {
704  dupKey->setFileName(""); // don't share filename
705  dupKey->modification();
706  }
707 
708  mEditor->layers()->notifyAnimationLengthChanged();
709 }
710 
711 void ActionCommands::moveFrameForward()
712 {
713  Layer* layer = mEditor->layers()->currentLayer();
714  if (layer)
715  {
716  if (layer->moveKeyFrameForward(mEditor->currentFrame()))
717  {
718  mEditor->scrubForward();
719  }
720  }
721 
722  mEditor->layers()->notifyAnimationLengthChanged();
723 }
724 
725 void ActionCommands::moveFrameBackward()
726 {
727  Layer* layer = mEditor->layers()->currentLayer();
728  if (layer)
729  {
730  if (layer->moveKeyFrameBackward(mEditor->currentFrame()))
731  {
732  mEditor->scrubBackward();
733  }
734  }
735 }
736 
737 Status ActionCommands::addNewBitmapLayer()
738 {
739  bool ok;
740  QString text = QInputDialog::getText(nullptr, tr("Layer Properties"),
741  tr("Layer name:"), QLineEdit::Normal,
742  mEditor->layers()->nameSuggestLayer(tr("Bitmap Layer")), &ok);
743  if (ok && !text.isEmpty())
744  {
745  mEditor->layers()->createBitmapLayer(text);
746  }
747  return Status::OK;
748 }
749 
750 Status ActionCommands::addNewVectorLayer()
751 {
752  bool ok;
753  QString text = QInputDialog::getText(nullptr, tr("Layer Properties"),
754  tr("Layer name:"), QLineEdit::Normal,
755  mEditor->layers()->nameSuggestLayer(tr("Vector Layer")), &ok);
756  if (ok && !text.isEmpty())
757  {
758  mEditor->layers()->createVectorLayer(text);
759  }
760  return Status::OK;
761 }
762 
763 Status ActionCommands::addNewCameraLayer()
764 {
765  bool ok;
766  QString text = QInputDialog::getText(nullptr, tr("Layer Properties", "A popup when creating a new layer"),
767  tr("Layer name:"), QLineEdit::Normal,
768  mEditor->layers()->nameSuggestLayer(tr("Camera Layer")), &ok);
769  if (ok && !text.isEmpty())
770  {
771  mEditor->layers()->createCameraLayer(text);
772  }
773  return Status::OK;
774 }
775 
776 Status ActionCommands::addNewSoundLayer()
777 {
778  bool ok = false;
779  QString strLayerName = QInputDialog::getText(nullptr, tr("Layer Properties"),
780  tr("Layer name:"), QLineEdit::Normal,
781  mEditor->layers()->nameSuggestLayer(tr("Sound Layer")), &ok);
782  if (ok && !strLayerName.isEmpty())
783  {
784  Layer* layer = mEditor->layers()->createSoundLayer(strLayerName);
785  mEditor->layers()->setCurrentLayer(layer);
786  }
787  return Status::OK;
788 }
789 
790 Status ActionCommands::deleteCurrentLayer()
791 {
792  LayerManager* layerMgr = mEditor->layers();
793  QString strLayerName = layerMgr->currentLayer()->name();
794 
795  int ret = QMessageBox::warning(mParent,
796  tr("Delete Layer", "Windows title of Delete current layer pop-up."),
797  tr("Are you sure you want to delete layer: %1? This cannot be undone.").arg(strLayerName),
800  if (ret == QMessageBox::Ok)
801  {
802  Status st = layerMgr->deleteLayer(mEditor->currentLayerIndex());
803  if (st == Status::ERROR_NEED_AT_LEAST_ONE_CAMERA_LAYER)
804  {
805  QMessageBox::information(mParent, "",
806  tr("Please keep at least one camera layer in project", "text when failed to delete camera layer"));
807  }
808  }
809  return Status::OK;
810 }
811 
812 void ActionCommands::setLayerVisibilityIndex(int index)
813 {
814  mEditor->setLayerVisibility(static_cast<LayerVisibility>(index));
815 }
816 
817 void ActionCommands::changeKeyframeLineColor()
818 {
819  if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP &&
820  mEditor->layers()->currentLayer()->keyExists(mEditor->currentFrame()))
821  {
822  QRgb color = mEditor->color()->frontColor().rgb();
823  LayerBitmap* layer = static_cast<LayerBitmap*>(mEditor->layers()->currentLayer());
824  layer->getBitmapImageAtFrame(mEditor->currentFrame())->fillNonAlphaPixels(color);
825  mEditor->updateFrame(mEditor->currentFrame());
826  }
827 }
828 
829 void ActionCommands::changeallKeyframeLineColor()
830 {
831  if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP)
832  {
833  QRgb color = mEditor->color()->frontColor().rgb();
834  LayerBitmap* layer = static_cast<LayerBitmap*>(mEditor->layers()->currentLayer());
835  for (int i = layer->firstKeyFramePosition(); i <= layer->getMaxKeyFramePosition(); i++)
836  {
837  if (layer->keyExists(i))
838  layer->getBitmapImageAtFrame(i)->fillNonAlphaPixels(color);
839  }
840  mEditor->updateFrame(mEditor->currentFrame());
841  }
842 }
843 
844 void ActionCommands::help()
845 {
846  QString url = "http://www.pencil2d.org/doc/";
848 }
849 
850 void ActionCommands::quickGuide()
851 {
853  QString sCopyDest = QDir(sDocPath).filePath("pencil2d_quick_guide.pdf");
854 
855  QFile quickGuideFile(":/app/pencil2d_quick_guide.pdf");
856  quickGuideFile.copy(sCopyDest);
857 
859 }
860 
861 void ActionCommands::website()
862 {
863  QString url = "https://www.pencil2d.org/";
865 }
866 
867 void ActionCommands::forum()
868 {
869  QString url = "https://discuss.pencil2d.org/";
871 }
872 
873 void ActionCommands::discord()
874 {
875  QString url = "https://discord.gg/8FxdV2g";
877 }
878 
879 void ActionCommands::reportbug()
880 {
881  QString url = "https://github.com/pencil2d/pencil/issues";
883 }
884 
885 void ActionCommands::checkForUpdates()
886 {
887  CheckUpdatesDialog dialog;
888  dialog.startChecking();
889  dialog.exec();
890 }
891 
892 // This action is a temporary measure until we have an automated recover mechanism in place
893 void ActionCommands::openTemporaryDirectory()
894 {
895  int ret = QMessageBox::warning(mParent, tr("Warning"), tr("The temporary directory is meant to be used only by Pencil2D. Do not modify it unless you know what you are doing."), QMessageBox::Cancel, QMessageBox::Ok);
896  if (ret == QMessageBox::Ok)
897  {
899  }
900 }
901 
902 void ActionCommands::about()
903 {
904  AboutDialog* aboutBox = new AboutDialog(mParent);
906  aboutBox->init();
907  aboutBox->exec();
908 }
QString writableLocation(QStandardPaths::StandardLocation type)
void setLayerVisibility(LayerVisibility visibility)
The visiblity value should match any of the VISIBILITY enum values.
Definition: editor.cpp:615
void setWindowModality(Qt::WindowModality windowModality)
QString filePath(const QString &fileName) const const
void setAttribute(Qt::WidgetAttribute attribute, bool on)
virtual int exec()
bool exists() const const
Definition: camera.h:24
QString tr(const char *sourceText, const char *disambiguation, int n)
QMessageBox::StandardButton information(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
void processEvents(QEventLoop::ProcessEventsFlags flags)
QMessageBox::StandardButton question(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
QRgb rgb() const const
QDir temp()
bool isEmpty() const const
void setText(const QString &text)
Definition: layer.h:39
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
int result() const const
void deleteLater()
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
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.
virtual int exec() override
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.
Definition: filedialog.cpp:27
WA_DeleteOnClose
void setWindowFlags(Qt::WindowFlags type)
void setWindowTitle(const QString &)
QMessageBox::StandardButton warning(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
void addButton(QAbstractButton *button, QMessageBox::ButtonRole role)
void show()
bool openUrl(const QUrl &url)
WindowModal
int animationLength(bool includeSounds=true)
Get the length of current project.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
typedef WindowFlags
QUrl fromLocalFile(const QString &localFile)