File indexing completed on 2024-06-16 04:16:18
0001 /* 0002 * SPDX-FileCopyrightText: 2019 Shi Yan <billconan@gmail.net> 0003 * SPDX-FileCopyrightText: 2020 Dmitrii Utkin <loentar@gmail.com> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.1-only 0006 */ 0007 0008 #include "recorderdocker_dock.h" 0009 #include "recorder_config.h" 0010 #include "recorder_writer.h" 0011 #include "recorder_const.h" 0012 #include "ui_recorderdocker.h" 0013 #include "recorder_snapshots_manager.h" 0014 #include "recorder_export.h" 0015 #include "recorder_export_settings.h" 0016 #include "recorder_export_config.h" 0017 0018 #include <klocalizedstring.h> 0019 #include <kis_action_registry.h> 0020 #include <kis_canvas2.h> 0021 #include <kis_icon_utils.h> 0022 #include <kis_statusbar.h> 0023 #include <KisDocument.h> 0024 #include <KisViewManager.h> 0025 #include <KoDocumentInfo.h> 0026 #include <kactioncollection.h> 0027 #include <KisPart.h> 0028 #include <KisKineticScroller.h> 0029 #include "KisMainWindow.h" 0030 0031 #include <QFileInfo> 0032 #include <QPointer> 0033 #include <QFileDialog> 0034 #include <QMessageBox> 0035 #include <QTimer> 0036 0037 namespace 0038 { 0039 const QString keyActionRecordToggle = "recorder_record_toggle"; 0040 const QString keyActionExport = "recorder_export"; 0041 0042 const QString activeColorGreen(" color='#5cab25'"); 0043 const QString inactiveColorGreen(" color='#b4e196'"); 0044 const QString activeColorOrange(" color='#ca8f14'"); 0045 const QString inactiveColorOrange(" color='#ffe5af'"); 0046 const QString activeColorRed(" color='#da4453'"); 0047 const QString inactiveColorRed(" color='#f2c4c9'"); 0048 const QString inactiveColorGray(" color='#3e3e3e'"); 0049 0050 const QColor textColorOrange(0xff, 0xe5, 0xaf); 0051 const QColor buttonColorOrange(0xca, 0x8f, 0x14); 0052 const QColor textColorRed(0xf2, 0xc4, 0xc9); 0053 const QColor buttonColorRed(0xda, 0x44, 0x53); 0054 0055 } 0056 0057 0058 class RecorderDockerDock::Private 0059 { 0060 public: 0061 RecorderDockerDock *const q; 0062 QScopedPointer<Ui::RecorderDocker> ui; 0063 QPalette threadsSliderPalette; 0064 QPalette threadsSpinPalette; 0065 QPointer<KisCanvas2> canvas; 0066 RecorderWriterManager writer; 0067 0068 QAction *recordToggleAction = nullptr; 0069 QAction *exportAction = nullptr; 0070 0071 QString snapshotDirectory; 0072 QString prefix; 0073 QString outputDirectory; 0074 double captureInterval = 0.; 0075 RecorderFormat format = RecorderFormat::JPEG; 0076 int quality = 0; 0077 int compression = 0; 0078 int resolution = 0; 0079 bool realTimeCaptureMode = false; 0080 bool recordIsolateLayerMode = false; 0081 bool recordAutomatically = false; 0082 bool paused = true; 0083 QTimer pausedTimer; 0084 QTimer warningTimer; 0085 0086 QLabel* statusBarLabel; 0087 QLabel* statusBarWarningLabel; 0088 0089 QMap<QString, bool> enabledIds; 0090 0091 Private(const RecorderExportSettings &es, RecorderDockerDock *q_ptr) 0092 : q(q_ptr) 0093 , ui(new Ui::RecorderDocker()) 0094 , writer(es) 0095 , statusBarLabel(new QLabel()) 0096 , statusBarWarningLabel(new QLabel()) 0097 { 0098 updateRecIndicator(); 0099 statusBarWarningLabel->setPixmap(KisIconUtils::loadIcon("warning").pixmap(16, 16)); 0100 statusBarWarningLabel->hide(); 0101 warningTimer.setInterval(10000); 0102 warningTimer.setSingleShot(true); 0103 pausedTimer.setSingleShot(true); 0104 connect(&warningTimer, SIGNAL(timeout()), q, SLOT(onWarningTimeout())); 0105 connect(&pausedTimer, SIGNAL(timeout()), q, SLOT(onPausedTimeout())); 0106 } 0107 0108 void loadSettings() 0109 { 0110 RecorderConfig config(true); 0111 snapshotDirectory = config.snapshotDirectory(); 0112 captureInterval = config.captureInterval(); 0113 format = config.format(); 0114 quality = config.quality(); 0115 compression = config.compression(); 0116 resolution = config.resolution(); 0117 writer.recorderThreads.set(config.threads()); 0118 realTimeCaptureMode = config.realTimeCaptureMode(); 0119 if (realTimeCaptureMode) { 0120 q->exportSettings->lockFps = true; 0121 q->exportSettings->realTimeCaptureModeWasSet = true; 0122 } 0123 recordIsolateLayerMode = config.recordIsolateLayerMode(); 0124 recordAutomatically = config.recordAutomatically(); 0125 0126 updateUiFormat(); 0127 } 0128 0129 void loadRelevantExportSettings() 0130 { 0131 RecorderExportConfig config(true); 0132 q->exportSettings->fps = config.fps(); 0133 } 0134 0135 void updateUiFormat() { 0136 int index = 0; 0137 QString title; 0138 QString hint; 0139 int minValue = 0; 0140 int maxValue = 0; 0141 QString suffix; 0142 int factor = 0; 0143 switch (format) { 0144 case RecorderFormat::JPEG: 0145 index = 0; 0146 title = i18nc("Title for label. JPEG Quality level", "Quality:"); 0147 hint = i18nc("@tooltip", "Greater value will produce a larger file and a better quality. Doesn't affect CPU consumption.\nValues lower than 50 are not recommended due to high artifacts."); 0148 minValue = 1; 0149 maxValue = 100; 0150 suffix = "%"; 0151 factor = quality; 0152 break; 0153 case RecorderFormat::PNG: 0154 index = 1; 0155 title = i18nc("Title for label. PNG Compression level", "Compression:"); 0156 hint = i18nc("@tooltip", "Greater value will produce a smaller file but will require more from your CPU. Doesn't affect quality.\nCompression set to 0 is not recommended due to high disk space consumption.\nValues above 3 are not recommended due to high performance impact."); 0157 minValue = 0; 0158 maxValue = 5; 0159 suffix = ""; 0160 factor = compression; 0161 break; 0162 } 0163 0164 ui->comboFormat->setCurrentIndex(index); 0165 ui->labelQuality->setText(title); 0166 ui->spinQuality->setToolTip(hint); 0167 QSignalBlocker blocker(ui->spinQuality); 0168 ui->spinQuality->setMinimum(minValue); 0169 ui->spinQuality->setMaximum(maxValue); 0170 ui->spinQuality->setValue(factor); 0171 ui->spinQuality->setSuffix(suffix); 0172 } 0173 0174 void updateUiForRealTimeMode() { 0175 QString title; 0176 double minValue = 0; 0177 double maxValue = 0; 0178 double value = 0; 0179 int decimals = 0; 0180 QString suffix; 0181 QSignalBlocker blocker(ui->spinRate); 0182 0183 if (realTimeCaptureMode) { 0184 title = i18nc("Title for label. Video frames per second", "Video FPS:"); 0185 minValue = 1; 0186 maxValue = 60; 0187 decimals = 0; 0188 value = q->exportSettings->fps; 0189 suffix = ""; 0190 disconnect(ui->spinRate, SIGNAL(valueChanged(double)), q, SLOT(onCaptureIntervalChanged(double))); 0191 connect(ui->spinRate, SIGNAL(valueChanged(double)), q, SLOT(onVideoFPSChanged(double))); 0192 } else { 0193 title = i18nc("Title for label. Capture rate", "Capture interval:"); 0194 minValue = 0.10; 0195 maxValue = 100.0; 0196 decimals = 1; 0197 value = captureInterval; 0198 suffix = " sec."; 0199 disconnect(ui->spinRate, SIGNAL(valueChanged(double)), q, SLOT(onVideoFPSChanged(double))); 0200 connect(ui->spinRate, SIGNAL(valueChanged(double)), q, SLOT(onCaptureIntervalChanged(double))); 0201 } 0202 0203 ui->labelRate->setText(title); 0204 ui->spinRate->setDecimals(decimals); 0205 ui->spinRate->setMinimum(minValue); 0206 ui->spinRate->setMaximum(maxValue); 0207 ui->spinRate->setSuffix(suffix); 0208 ui->spinRate->setValue(value); 0209 } 0210 0211 void updateWriterSettings() 0212 { 0213 outputDirectory = snapshotDirectory % QDir::separator() % prefix % QDir::separator(); 0214 writer.setup({ 0215 outputDirectory, 0216 format, 0217 quality, 0218 compression, 0219 resolution, 0220 captureInterval, 0221 recordIsolateLayerMode, 0222 realTimeCaptureMode}); 0223 } 0224 0225 QString getPrefix() 0226 { 0227 return !canvas ? "" 0228 : canvas->imageView()->document()->documentInfo()->aboutInfo("creation-date").remove(QRegExp("[^0-9]")); 0229 } 0230 0231 void updateComboResolution(quint32 width, quint32 height) 0232 { 0233 const QStringList titles = { 0234 i18nc("Use original resolution for the frames when recording the canvas", "Original"), 0235 i18nc("Use the resolution two times smaller than the original resolution for the frames when recording the canvas", "Half"), 0236 i18nc("Use the resolution four times smaller than the original resolution for the frames when recording the canvas", "Quarter") 0237 }; 0238 0239 QStringList items; 0240 for (int index = 0, len = titles.length(); index < len; ++index) { 0241 int divider = 1 << index; 0242 items += QString("%1 (%2x%3)").arg(titles[index]) 0243 .arg((width / divider) & ~1) 0244 .arg((height / divider) & ~1); 0245 } 0246 QSignalBlocker blocker(ui->comboResolution); 0247 const int currentIndex = ui->comboResolution->currentIndex(); 0248 ui->comboResolution->clear(); 0249 ui->comboResolution->addItems(items); 0250 ui->comboResolution->setCurrentIndex(currentIndex); 0251 } 0252 0253 void updateRecordStatus(bool isRecording) 0254 { 0255 recordToggleAction->setChecked(isRecording); 0256 recordToggleAction->setEnabled(true); 0257 0258 QSignalBlocker blocker(ui->buttonRecordToggle); 0259 ui->buttonRecordToggle->setChecked(isRecording); 0260 ui->buttonRecordToggle->setIcon(KisIconUtils::loadIcon(isRecording ? "media-playback-stop" : "media-record")); 0261 ui->buttonRecordToggle->setText(isRecording ? i18nc("Stop recording the canvas", "Stop") 0262 : i18nc("Start recording the canvas", "Record")); 0263 ui->buttonRecordToggle->setEnabled(true); 0264 0265 ui->widgetSettings->setEnabled(!isRecording); 0266 0267 statusBarLabel->setVisible(isRecording); 0268 0269 if (!canvas) 0270 return; 0271 0272 KisStatusBar *statusBar = canvas->viewManager()->statusBar(); 0273 if (isRecording) { 0274 statusBar->addExtraWidget(statusBarWarningLabel); 0275 statusBar->addExtraWidget(statusBarLabel); 0276 updateRecIndicator(); 0277 } else { 0278 statusBar->removeExtraWidget(statusBarWarningLabel); 0279 statusBar->removeExtraWidget(statusBarLabel); 0280 } 0281 } 0282 0283 void updateRecIndicator() 0284 { 0285 auto threads = writer.recorderThreads.get(); 0286 auto threadsInUse = writer.recorderThreads.getUsed(); 0287 QString label("<font style='letter-spacing:-4px'>"); 0288 QString activeColor; 0289 QString inactiveColor; 0290 for (unsigned int threadNr = 1; threadNr <= ThreadSystemValue::MaxThreadCount ; threadNr++) 0291 { 0292 if (threadNr > threads) { 0293 activeColor = inactiveColorGray; 0294 inactiveColor = inactiveColorGray; 0295 } else if (threadNr > ThreadSystemValue::MaxRecordThreadCount) { 0296 activeColor = activeColorRed; 0297 inactiveColor = inactiveColorRed; 0298 } else if (threadNr > ThreadSystemValue::IdealRecordThreadCount) { 0299 activeColor = activeColorOrange; 0300 inactiveColor = inactiveColorOrange; 0301 } else { 0302 activeColor = activeColorGreen; 0303 inactiveColor = inactiveColorGreen; 0304 } 0305 label.append(QString("<font%1>▍</font>") 0306 .arg(threadNr <= threadsInUse ? activeColor : inactiveColor)); 0307 } 0308 // don't remove empty <font></font> tag else label will jump a few pixels around 0309 label.append(QString("</font><font> %1 </font><font%2>●</font>") 0310 .arg(i18nc("Recording symbol", "REC")) 0311 .arg(paused ? "" : activeColorRed)); 0312 statusBarLabel->setText(label); 0313 statusBarLabel->setToolTip(paused ? i18n("Recorder is paused") : QString(i18n("Active recording with %1 of %2 available threads")).arg(threadsInUse).arg(threads)); 0314 } 0315 0316 void showWarning(const QString &hint) { 0317 if (statusBarWarningLabel->isHidden()) { 0318 statusBarWarningLabel->setToolTip(hint); 0319 statusBarWarningLabel->show(); 0320 warningTimer.start(); 0321 } 0322 } 0323 0324 void updateThreadUi() 0325 { 0326 QString toolTipText; 0327 auto threads = writer.recorderThreads.get(); 0328 if (threads > ThreadSystemValue::MaxRecordThreadCount) { 0329 // Number of threads exceeds ideal thread count 0330 // -> switch color of threads slider and spin wheel to red 0331 QPalette pal; 0332 pal.setColor(QPalette::Text, textColorRed); 0333 pal.setColor(QPalette::Button, buttonColorRed); 0334 ui->spinThreads->setPalette(pal); 0335 ui->sliderThreads->setPalette(pal); 0336 toolTipText = QString( 0337 i18n("Set the number of recording threads.\nThe number of threads exceeds the ideal max number of your hardware setup.\nPlease be aware, that a number greater than %1 probably won't give you any performance boost.") 0338 .arg(ThreadSystemValue::MaxRecordThreadCount)); 0339 } else if (threads > ThreadSystemValue::IdealRecordThreadCount) { 0340 // Number of threads exceeds ideal recorder thread count 0341 // -> switch color of threads slider and spin wheel to orange 0342 QPalette pal; 0343 pal.setColor(QPalette::Text, textColorOrange); 0344 pal.setColor(QPalette::Button, buttonColorOrange); 0345 ui->spinThreads->setPalette(pal); 0346 ui->sliderThreads->setPalette(pal); 0347 toolTipText = QString( 0348 i18n("Set the number of recording threads.\nAccordingly to your hardware setup you should record with no more than %1 threads.\nYou can play around with one or two more threads, but keep an eye on your overall system performance.") 0349 .arg(ThreadSystemValue::IdealRecordThreadCount)); 0350 } else { 0351 ui->spinThreads->setPalette(threadsSpinPalette); 0352 ui->sliderThreads->setPalette(threadsSliderPalette); 0353 toolTipText = i18n("Set the number of threads to be used for recording."); 0354 } 0355 ui->spinThreads->setToolTip(toolTipText); 0356 ui->sliderThreads->setToolTip(toolTipText); 0357 } 0358 }; 0359 0360 RecorderDockerDock::RecorderDockerDock() 0361 : QDockWidget(i18nc("Title of the docker", "Recorder")) 0362 , exportSettings(new RecorderExportSettings()) 0363 , d(new Private(*exportSettings, this)) 0364 { 0365 QWidget* page = new QWidget(this); 0366 d->ui->setupUi(page); 0367 0368 d->ui->buttonManageRecordings->setIcon(KisIconUtils::loadIcon("configure-thicker")); 0369 d->ui->buttonBrowse->setIcon(KisIconUtils::loadIcon("folder")); 0370 d->ui->buttonRecordToggle->setIcon(KisIconUtils::loadIcon("media-record")); 0371 d->ui->buttonExport->setIcon(KisIconUtils::loadIcon("document-export-16")); 0372 d->ui->sliderThreads->setTickPosition(QSlider::TickPosition::TicksBelow); 0373 d->ui->sliderThreads->setMinimum(1); 0374 d->ui->sliderThreads->setMaximum(ThreadSystemValue::MaxThreadCount); 0375 d->ui->spinThreads->setMinimum(1); 0376 d->ui->spinThreads->setMaximum(ThreadSystemValue::MaxThreadCount); 0377 d->threadsSpinPalette = d->ui->spinThreads->palette(); 0378 d->threadsSliderPalette = d->ui->sliderThreads->palette(); 0379 0380 d->loadSettings(); 0381 d->loadRelevantExportSettings(); 0382 d->updateThreadUi(); 0383 0384 d->ui->editDirectory->setText(d->snapshotDirectory); 0385 d->ui->spinQuality->setValue(d->quality); 0386 d->ui->spinThreads->setValue(d->writer.recorderThreads.get()); 0387 d->ui->comboResolution->setCurrentIndex(d->resolution); 0388 d->ui->checkBoxRealTimeCaptureMode->setChecked(d->realTimeCaptureMode); 0389 d->ui->checkBoxRecordIsolateMode->setChecked(d->recordIsolateLayerMode); 0390 d->ui->checkBoxAutoRecord->setChecked(d->recordAutomatically); 0391 0392 KisActionRegistry *actionRegistry = KisActionRegistry::instance(); 0393 d->recordToggleAction = actionRegistry->makeQAction(keyActionRecordToggle, this); 0394 d->exportAction = actionRegistry->makeQAction(keyActionExport, this); 0395 0396 connect(d->recordToggleAction, SIGNAL(toggled(bool)), d->ui->buttonRecordToggle, SLOT(setChecked(bool))); 0397 connect(d->exportAction, SIGNAL(triggered()), d->ui->buttonExport, SIGNAL(clicked())); 0398 connect(d->ui->buttonRecordToggle, SIGNAL(toggled(bool)), d->ui->buttonExport, SLOT(setDisabled(bool))); 0399 if (d->recordAutomatically) 0400 d->ui->buttonExport->setDisabled(true); 0401 0402 // Need to register toolbar actions before attaching canvas else it wont appear after restart. 0403 // Is there any better way to do this? 0404 connect(KisPart::instance(), SIGNAL(sigMainWindowIsBeingCreated(KisMainWindow *)), 0405 this, SLOT(onMainWindowIsBeingCreated(KisMainWindow *))); 0406 0407 connect(d->ui->buttonManageRecordings, SIGNAL(clicked()), this, SLOT(onManageRecordingsButtonClicked())); 0408 connect(d->ui->buttonBrowse, SIGNAL(clicked()), this, SLOT(onSelectRecordFolderButtonClicked())); 0409 connect(d->ui->comboFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(onFormatChanged(int))); 0410 connect(d->ui->spinQuality, SIGNAL(valueChanged(int)), this, SLOT(onQualityChanged(int))); 0411 connect(d->ui->spinThreads, SIGNAL(valueChanged(int)), this, SLOT(onThreadsChanged(int))); 0412 connect(d->ui->comboResolution, SIGNAL(currentIndexChanged(int)), this, SLOT(onResolutionChanged(int))); 0413 connect(d->ui->checkBoxRealTimeCaptureMode, SIGNAL(toggled(bool)), this, SLOT(onRealTimeCaptureModeToggled(bool))); 0414 connect(d->ui->checkBoxRecordIsolateMode, SIGNAL(toggled(bool)), this, SLOT(onRecordIsolateLayerModeToggled(bool))); 0415 connect(d->ui->checkBoxAutoRecord, SIGNAL(toggled(bool)), this, SLOT(onAutoRecordToggled(bool))); 0416 connect(d->ui->buttonRecordToggle, SIGNAL(toggled(bool)), this, SLOT(onRecordButtonToggled(bool))); 0417 connect(d->ui->buttonExport, SIGNAL(clicked()), this, SLOT(onExportButtonClicked())); 0418 0419 connect(&d->writer.recorderThreads, SIGNAL(notifyInUseChange(bool)), this, SLOT(onActiveRecording(bool))); 0420 connect(&d->writer.recorderThreads, SIGNAL(notifyInUseChange(bool)), this, SLOT(onUpdateRecIndicator())); 0421 connect(&d->writer, SIGNAL(started()), this, SLOT(onWriterStarted())); 0422 connect(&d->writer, SIGNAL(stopped()), this, SLOT(onWriterStopped())); 0423 connect(&d->writer, SIGNAL(frameWriteFailed()), this, SLOT(onWriterFrameWriteFailed())); 0424 connect(&d->writer, SIGNAL(recorderStopWarning()), this, SLOT(onRecorderStopWarning())); 0425 connect(&d->writer, SIGNAL(lowPerformanceWarning()), this, SLOT(onLowPerformanceWarning())); 0426 0427 0428 QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(d->ui->scrollArea); 0429 if (scroller) { 0430 connect(scroller, SIGNAL(stateChanged(QScroller::State)), 0431 this, SLOT(slotScrollerStateChanged(QScroller::State))); 0432 } 0433 0434 // The system is not efficient enough for the RealTime Recording Feature 0435 if (ThreadSystemValue::MaxRecordThreadCount <= 1) 0436 { 0437 d->ui->checkBoxRealTimeCaptureMode->setCheckState(Qt::Unchecked); 0438 d->ui->checkBoxRealTimeCaptureMode->setDisabled(true); 0439 d->ui->checkBoxRealTimeCaptureMode->setToolTip( 0440 i18n("Your system is not efficient enough for this feature")); 0441 } 0442 0443 setWidget(page); 0444 } 0445 0446 RecorderDockerDock::~RecorderDockerDock() 0447 { 0448 delete d; 0449 delete exportSettings; 0450 } 0451 0452 void RecorderDockerDock::setCanvas(KoCanvasBase* canvas) 0453 { 0454 setEnabled(canvas != nullptr); 0455 0456 if (d->canvas == canvas) 0457 return; 0458 0459 d->canvas = dynamic_cast<KisCanvas2*>(canvas); 0460 d->writer.setCanvas(d->canvas); 0461 0462 if (!d->canvas) 0463 return; 0464 0465 KisDocument *document = d->canvas->imageView()->document(); 0466 if (d->recordAutomatically && !d->enabledIds.contains(document->linkedResourcesStorageId())) 0467 onRecordButtonToggled(true); 0468 0469 d->updateComboResolution(document->image()->width(), document->image()->height()); 0470 0471 d->prefix = d->getPrefix(); 0472 d->updateWriterSettings(); 0473 d->updateUiFormat(); 0474 d->updateUiForRealTimeMode(); 0475 0476 bool enabled = d->enabledIds.value(document->linkedResourcesStorageId(), false); 0477 d->writer.setEnabled(enabled); 0478 d->updateRecordStatus(enabled); 0479 } 0480 0481 void RecorderDockerDock::unsetCanvas() 0482 { 0483 d->updateRecordStatus(false); 0484 d->recordToggleAction->setChecked(false); 0485 setEnabled(false); 0486 d->writer.stop(); 0487 d->writer.setCanvas(nullptr); 0488 d->canvas = nullptr; 0489 d->enabledIds.clear(); 0490 } 0491 0492 void RecorderDockerDock::onMainWindowIsBeingCreated(KisMainWindow *window) 0493 { 0494 KisKActionCollection *actionCollection = window->viewManager()->actionCollection(); 0495 actionCollection->addAction(keyActionRecordToggle, d->recordToggleAction); 0496 actionCollection->addAction(keyActionExport, d->exportAction); 0497 } 0498 0499 void RecorderDockerDock::onRecordButtonToggled(bool checked) 0500 { 0501 QSignalBlocker blocker(d->ui->buttonRecordToggle); 0502 d->recordToggleAction->setChecked(checked); 0503 0504 if (!d->canvas) 0505 return; 0506 0507 const QString &id = d->canvas->imageView()->document()->linkedResourcesStorageId(); 0508 0509 bool wasEmpty = !d->enabledIds.values().contains(true); 0510 0511 d->enabledIds[id] = checked; 0512 0513 bool isEmpty = !d->enabledIds.values().contains(true); 0514 0515 d->writer.setEnabled(checked); 0516 0517 if (isEmpty == wasEmpty) { 0518 d->updateRecordStatus(checked); 0519 return; 0520 } 0521 0522 0523 d->ui->buttonRecordToggle->setEnabled(false); 0524 0525 if (checked) { 0526 d->updateWriterSettings(); 0527 d->updateUiFormat(); 0528 d->writer.start(); 0529 0530 // Calculate Rec symbol activity timeout depending on the capture interval 0531 // The pausedTimer interval is set to a slightly greater value than the capture interval 0532 // to avoid flickering for ongoing painting. This is also the reason for the min and max 0533 // values 305 and 2005 (instead of 300 and 2000, respectively). 0534 if (d->realTimeCaptureMode) { 0535 d->pausedTimer.setInterval(qBound(305, static_cast<int>(1000.0/static_cast<double>(exportSettings->fps)) + 5,2005)); 0536 } else { 0537 d->pausedTimer.setInterval(qBound(305, static_cast<int>(qMax(d->captureInterval, .1) * 1000.0) + 5, 2005)); 0538 } 0539 } else { 0540 d->writer.stop(); 0541 d->warningTimer.stop(); 0542 d->pausedTimer.stop(); 0543 d->statusBarWarningLabel->hide(); 0544 d->paused = true; 0545 } 0546 0547 } 0548 0549 void RecorderDockerDock::onExportButtonClicked() 0550 { 0551 if (!d->canvas) 0552 return; 0553 0554 KisDocument *document = d->canvas->imageView()->document(); 0555 0556 exportSettings->videoFileName = QFileInfo(document->caption().trimmed()).completeBaseName(); 0557 exportSettings->inputDirectory = d->outputDirectory; 0558 exportSettings->format = d->format; 0559 exportSettings->realTimeCaptureMode = d->realTimeCaptureMode; 0560 0561 RecorderExport exportDialog(exportSettings, this); 0562 exportDialog.setup(); 0563 exportDialog.exec(); 0564 0565 if (d->realTimeCaptureMode) 0566 d->ui->spinRate->setValue(exportSettings->fps); 0567 } 0568 0569 void RecorderDockerDock::onManageRecordingsButtonClicked() 0570 { 0571 RecorderSnapshotsManager snapshotsManager(this); 0572 snapshotsManager.execFor(d->snapshotDirectory); 0573 } 0574 0575 0576 void RecorderDockerDock::onSelectRecordFolderButtonClicked() 0577 { 0578 QFileDialog dialog(this); 0579 dialog.setFileMode(QFileDialog::DirectoryOnly); 0580 const QString &directory = dialog.getExistingDirectory(this, 0581 i18n("Select a Directory for Recordings"), 0582 d->ui->editDirectory->text(), 0583 QFileDialog::ShowDirsOnly); 0584 if (!directory.isEmpty()) { 0585 d->ui->editDirectory->setText(directory); 0586 RecorderConfig(false).setSnapshotDirectory(directory); 0587 d->loadSettings(); 0588 } 0589 } 0590 0591 void RecorderDockerDock::onRecordIsolateLayerModeToggled(bool checked) 0592 { 0593 d->recordIsolateLayerMode = checked; 0594 RecorderConfig(false).setRecordIsolateLayerMode(checked); 0595 d->loadSettings(); 0596 } 0597 0598 void RecorderDockerDock::onAutoRecordToggled(bool checked) 0599 { 0600 d->recordAutomatically = checked; 0601 RecorderConfig(false).setRecordAutomatically(checked); 0602 d->loadSettings(); 0603 } 0604 0605 void RecorderDockerDock::onRealTimeCaptureModeToggled(bool checked) 0606 { 0607 d->realTimeCaptureMode = checked; 0608 RecorderConfig(false).setRealTimeCaptureMode(checked); 0609 d->loadSettings(); 0610 d->updateUiForRealTimeMode(); 0611 if (d->realTimeCaptureMode) { 0612 exportSettings->lockFps = true; 0613 exportSettings->realTimeCaptureModeWasSet = true; 0614 } 0615 } 0616 0617 void RecorderDockerDock::onCaptureIntervalChanged(double interval) 0618 { 0619 d->captureInterval = interval; 0620 RecorderConfig(false).setCaptureInterval(interval); 0621 d->loadSettings(); 0622 } 0623 void RecorderDockerDock::onVideoFPSChanged(double fps) 0624 { 0625 exportSettings->fps = fps; 0626 RecorderExportConfig(false).setFps(fps); 0627 d->loadRelevantExportSettings(); 0628 } 0629 0630 void RecorderDockerDock::onQualityChanged(int value) 0631 { 0632 switch (d->format) { 0633 case RecorderFormat::JPEG: 0634 d->quality = value; 0635 RecorderConfig(false).setQuality(value); 0636 d->loadSettings(); 0637 break; 0638 case RecorderFormat::PNG: 0639 d->compression = value; 0640 RecorderConfig(false).setCompression(value); 0641 d->loadSettings(); 0642 break; 0643 } 0644 } 0645 0646 void RecorderDockerDock::onFormatChanged(int format) 0647 { 0648 d->format = static_cast<RecorderFormat>(format); 0649 d->updateUiFormat(); 0650 0651 RecorderConfig(false).setFormat(d->format); 0652 d->loadSettings(); 0653 } 0654 0655 void RecorderDockerDock::onResolutionChanged(int resolution) 0656 { 0657 d->resolution = resolution; 0658 RecorderConfig(false).setResolution(resolution); 0659 d->loadSettings(); 0660 } 0661 0662 void RecorderDockerDock::onThreadsChanged(int threads) 0663 { 0664 d->writer.recorderThreads.set(threads); 0665 RecorderConfig(false).setThreads(threads); 0666 d->loadSettings(); 0667 d->updateThreadUi(); 0668 } 0669 0670 void RecorderDockerDock::onWriterStarted() 0671 { 0672 d->updateRecordStatus(true); 0673 } 0674 0675 void RecorderDockerDock::onWriterStopped() 0676 { 0677 d->updateRecordStatus(false); 0678 } 0679 0680 void RecorderDockerDock::onUpdateRecIndicator() 0681 { 0682 d->updateRecIndicator(); 0683 } 0684 0685 void RecorderDockerDock::onActiveRecording(bool valueWasIncreased) 0686 { 0687 if (!valueWasIncreased) 0688 return; 0689 0690 d->paused = false; 0691 d->pausedTimer.start(); 0692 } 0693 0694 void RecorderDockerDock::onPausedTimeout() 0695 { 0696 d->paused = true; 0697 d->updateRecIndicator(); 0698 } 0699 0700 void RecorderDockerDock::onWriterFrameWriteFailed() 0701 { 0702 QMessageBox::warning(this, i18nc("@title:window", "Recorder"), 0703 i18n("The recorder has been stopped due to failure while writing a frame. Please check free disk space and start the recorder again.")); 0704 } 0705 0706 void RecorderDockerDock::onRecorderStopWarning() 0707 { 0708 QMessageBox::warning(this, i18nc("@title:window", "Recorder"), 0709 i18n("Krita was unable to stop the recorder probably. Please try to restart Krita.")); 0710 } 0711 void RecorderDockerDock::onLowPerformanceWarning() 0712 { 0713 if (d->realTimeCaptureMode) { 0714 d->showWarning(i18n("Low performance warning. The recorder is not able to write all the frames in time during Real Time Capture mode.\nTry to reduce the frame rate for the ffmpeg export or reduce the scaling filtering in the canvas acceleration settings.")); 0715 } else { 0716 d->showWarning(i18n("Low performance warning. The recorder is not able to write all the frames in time.\nTry to increase the capture interval or reduce the scaling filtering in the canvas acceleration settings.")); 0717 } 0718 } 0719 0720 void RecorderDockerDock::onWarningTimeout() 0721 { 0722 d->statusBarWarningLabel->hide(); 0723 } 0724 0725 void RecorderDockerDock::slotScrollerStateChanged(QScroller::State state) 0726 { 0727 KisKineticScroller::updateCursor(this, state); 0728 }