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 }