File indexing completed on 2024-05-26 04:32:23

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Jouni Pentikäinen <joupent@gmail.com>
0003  *  SPDX-FileCopyrightText: 2020 Emmet O 'Neill <emmetoneill.pdx@gmail.com>
0004  *  SPDX-FileCopyrightText: 2020 Eoin O 'Neill <eoinoneill1991@gmail.com>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "KisAnimTimelineDocker.h"
0010 
0011 #include <QPointer>
0012 #include "QHBoxLayout"
0013 #include "QVBoxLayout"
0014 #include "QFormLayout"
0015 #include "QLabel"
0016 #include "QToolButton"
0017 #include "QMenu"
0018 #include "QWidgetAction"
0019 
0020 #include "krita_utils.h"
0021 #include "kis_canvas2.h"
0022 #include "kis_image.h"
0023 #include <KoIcon.h>
0024 #include "KisViewManager.h"
0025 #include "kis_paint_layer.h"
0026 #include "KisDocument.h"
0027 #include "kis_dummies_facade.h"
0028 #include "kis_shape_controller.h"
0029 #include "kis_action.h"
0030 #include "kis_action_manager.h"
0031 #include "KisCanvasAnimationState.h"
0032 #include "animation/KisFrameDisplayProxy.h"
0033 #include "KisAnimUtils.h"
0034 #include "kis_image_config.h"
0035 #include "kis_keyframe_channel.h"
0036 #include "kis_image.h"
0037 #include "KisPart.h"
0038 #include "KisPlaybackEngine.h"
0039 
0040 #include "KisAnimTimelineFramesModel.h"
0041 #include "KisAnimTimelineFramesView.h"
0042 #include "kis_time_span.h"
0043 #include "kis_animation_frame_cache.h"
0044 #include "kis_image_animation_interface.h"
0045 #include "kis_signal_auto_connection.h"
0046 #include "kis_node_manager.h"
0047 #include "kis_transport_controls.h"
0048 #include "kis_int_parse_spin_box.h"
0049 #include "kis_slider_spin_box.h"
0050 #include "kis_signals_blocker.h"
0051 #include "KisMainWindow.h"
0052 #include "KisAnimationPlaybackControlsModel.h"
0053 #include "KisWidgetConnectionUtils.h"
0054 #include "KisImageConfigNotifier.h"
0055 #include <KisSpinBoxI18nHelper.h>
0056 
0057 
0058 KisAnimTimelineDockerTitlebar::KisAnimTimelineDockerTitlebar(QWidget* parent) :
0059     KisUtilityTitleBar(new QLabel(i18n("Animation Timeline"), parent), parent)
0060 {
0061     setFocusPolicy(Qt::ClickFocus);
0062 
0063     // Transport Controls...
0064     transport = new KisTransportControls(this);
0065     transport->showSkipButtons(true);
0066     widgetAreaLayout->addWidget(transport);
0067 
0068     widgetAreaLayout->addSpacing(SPACING_UNIT);
0069 
0070     // Frame Register...
0071     frameRegister = new KisIntParseSpinBox(this);
0072     frameRegister->setToolTip(i18n("Frame register"));
0073     frameRegister->setPrefix("#  ");
0074     frameRegister->setRange(0, MAX_FRAMES);
0075     widgetAreaLayout->addWidget(frameRegister);
0076 
0077     widgetAreaLayout->addSpacing(SPACING_UNIT);
0078 
0079     {   // Drop Frames..
0080         btnDropFrames = new QToolButton(this);
0081         btnDropFrames->setAutoRaise(true);
0082         widgetAreaLayout->addWidget(btnDropFrames);
0083 
0084         // Playback Speed..
0085         sbSpeed = new KisSliderSpinBox(this);
0086         sbSpeed->setRange(25, 200);
0087         sbSpeed->setSingleStep(5);
0088         sbSpeed->setValue(100);
0089         sbSpeed->setPrefix(i18nc("preview playback speed percentage prefix", "Speed: "));
0090         sbSpeed->setSuffix(" %");
0091         sbSpeed->setToolTip(i18n("Preview playback speed"));
0092 
0093         widgetAreaLayout->addWidget(sbSpeed);
0094     }
0095 
0096     widgetAreaLayout->addSpacing(SPACING_UNIT);
0097 
0098     {   // Frame ops...
0099         QWidget *widget = new QWidget(this);
0100         QHBoxLayout *layout = new QHBoxLayout(widget);
0101         layout->setSpacing(0);
0102         layout->setContentsMargins(0,0,0,0);
0103 
0104         btnAddKeyframe = new QToolButton(this);
0105         btnAddKeyframe->setAutoRaise(true);
0106         layout->addWidget(btnAddKeyframe);
0107 
0108         btnDuplicateKeyframe = new QToolButton(this);
0109         btnDuplicateKeyframe->setAutoRaise(true);
0110         layout->addWidget(btnDuplicateKeyframe);
0111 
0112         btnRemoveKeyframe = new QToolButton(this);
0113         btnRemoveKeyframe->setAutoRaise(true);
0114         layout->addWidget(btnRemoveKeyframe);
0115 
0116         widgetAreaLayout->addWidget(widget);
0117     }
0118 
0119     widgetAreaLayout->addStretch();
0120 
0121     {   // Menus..
0122         QWidget *widget = new QWidget(this);
0123 
0124         QHBoxLayout *layout = new QHBoxLayout(widget);
0125         layout->setSpacing(0);
0126         layout->setContentsMargins(SPACING_UNIT,0,0,0);
0127 
0128         // Onion skins menu.
0129         btnOnionSkinsMenu = new QToolButton(this);
0130         btnOnionSkinsMenu->setIcon(KisIconUtils::loadIcon("onion_skin_options"));
0131         btnOnionSkinsMenu->setToolTip(i18n("Onion skins menu"));
0132         btnOnionSkinsMenu->setIconSize(QSize(22, 22));
0133         btnOnionSkinsMenu->setAutoRaise(true);
0134         layout->addWidget(btnOnionSkinsMenu);
0135 
0136         {   // Audio menu..
0137             QMenu *audioMenu = new QMenu(this);
0138 
0139             strImportAudio = QString(i18nc("@item:inmenu Load audio file into Krita from disk.", "Import Audio..."));
0140             importAudioAction = new QAction(strImportAudio, audioMenu);
0141             removeAudioAction = new QAction(i18nc("@item:inmenu", "Remove audio"), audioMenu);
0142 
0143             muteAudioAction = new QAction(i18nc("@item:inmenu Mute audio playback.", "Mute"), audioMenu);
0144             muteAudioAction->setCheckable(true);
0145 
0146             volumeSlider = new KisSliderSpinBox(audioMenu);
0147             volumeSlider->setRange(0, 100);
0148             KisSpinBoxI18nHelper::setText(
0149                 volumeSlider,
0150                 i18nc("@item:inmenu Volume slider; {n} is the number value, % is the percent sign", "Volume: {n}%"));
0151             volumeSlider->setSingleStep(1);
0152             volumeSlider->setPageStep(10);
0153             volumeSlider->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
0154 
0155             QWidgetAction *volumeAction = new QWidgetAction(audioMenu);
0156             volumeAction->setDefaultWidget(volumeSlider);
0157 
0158             audioMenu->addSeparator();
0159 
0160             audioMenu->addAction(importAudioAction);
0161             audioMenu->addAction(removeAudioAction);
0162 
0163             audioMenu->addSeparator();
0164 
0165             audioMenu->addAction(volumeAction);
0166             audioMenu->addAction(muteAudioAction);
0167 
0168             btnAudioMenu = new QToolButton(this);
0169             btnAudioMenu->setIcon(KisIconUtils::loadIcon("audio-none"));
0170             btnAudioMenu->setToolTip(i18n("Animation audio menu"));
0171             btnAudioMenu->setIconSize(QSize(22, 22));
0172             btnAudioMenu->setAutoRaise(true);
0173 
0174             btnAudioMenu->setPopupMode(QToolButton::InstantPopup);
0175             btnAudioMenu->setMenu(audioMenu);
0176             btnAudioMenu->setEnabled(false); // To be enabled on set canvas...
0177 
0178             layout->addWidget(btnAudioMenu);
0179         }
0180 
0181         {   // Settings menu..
0182             btnSettingsMenu = new QToolButton(this);
0183             btnSettingsMenu->setIcon(KisIconUtils::loadIcon("view-choose-22"));
0184             btnSettingsMenu->setToolTip(i18n("Animation settings menu"));
0185             btnSettingsMenu->setIconSize(QSize(22, 22));
0186             btnSettingsMenu->setAutoRaise(true);
0187 
0188             QWidget *settingsMenuWidget = new QWidget(this);
0189             QHBoxLayout *settingsMenuLayout = new QHBoxLayout(settingsMenuWidget);
0190 
0191             QWidget *fields = new QWidget(settingsMenuWidget);
0192             QFormLayout *fieldsLayout = new QFormLayout(fields);
0193 
0194             sbStartFrame = new KisIntParseSpinBox(settingsMenuWidget);
0195             sbStartFrame->setMaximum(10000);
0196             fieldsLayout->addRow(i18n("Clip Start: "), sbStartFrame);
0197 
0198             sbEndFrame = new KisIntParseSpinBox(settingsMenuWidget);
0199             sbEndFrame->setMaximum(10000);
0200             fieldsLayout->addRow(i18n("Clip End: "), sbEndFrame);
0201 
0202             sbFrameRate = new KisIntParseSpinBox(settingsMenuWidget);
0203             sbFrameRate->setMinimum(0);
0204             sbFrameRate->setMaximum(120);
0205             fieldsLayout->addRow(i18n("Frame Rate: "), sbFrameRate);
0206 
0207             QWidget *buttons = new QWidget(settingsMenuWidget);
0208             QVBoxLayout *buttonsLayout = new QVBoxLayout(buttons);
0209             buttonsLayout->setAlignment(Qt::AlignTop);
0210 
0211             {   // AutoKey..
0212                 // AutoKey Actions & Action Group..
0213                 autoKeyBlank = new QAction(i18n("AutoKey Blank"), this);
0214                 autoKeyBlank->setCheckable(true);
0215                 autoKeyDuplicate = new QAction(i18n("AutoKey Duplicate"), this);
0216                 autoKeyDuplicate->setCheckable(true);
0217                 QActionGroup *autoKeyModes = new QActionGroup(this);
0218                 autoKeyModes->addAction(autoKeyBlank);
0219                 autoKeyModes->addAction(autoKeyDuplicate);
0220                 autoKeyModes->setExclusive(true);
0221 
0222                 connect(autoKeyModes, &QActionGroup::triggered, this, [this](QAction* modeAction){
0223                     if (!modeAction) return;
0224                     {
0225                         KisImageConfig  imageCfg(false);
0226                         if (modeAction == autoKeyBlank) {
0227                             imageCfg.setAutoKeyModeDuplicate(false);
0228                         } else if (modeAction == autoKeyDuplicate) {
0229                             imageCfg.setAutoKeyModeDuplicate(true);
0230                         }
0231                     }
0232                     KisImageConfigNotifier::instance()->notifyAutoKeyFrameConfigurationChanged();
0233                 });
0234 
0235                 // AutoKey Mode Menu..
0236                 QMenu *autoKeyModeMenu = new QMenu(settingsMenuWidget);
0237                 autoKeyModeMenu->addActions(autoKeyModes->actions());
0238 
0239                 // AutoKey Button..
0240                 btnAutoKey = new QToolButton(settingsMenuWidget);
0241                 btnAutoKey->setMenu(autoKeyModeMenu);
0242                 btnAutoKey->setPopupMode(QToolButton::MenuButtonPopup);
0243                 buttonsLayout->addWidget(btnAutoKey);
0244             }
0245 
0246             settingsMenuLayout->addWidget(fields);
0247             settingsMenuLayout->addWidget(buttons);
0248 
0249             layout->addWidget(btnSettingsMenu);
0250 
0251             QMenu *settingsPopMenu = new QMenu(this);
0252             QWidgetAction *settingsMenuAction = new QWidgetAction(this);
0253             settingsMenuAction->setDefaultWidget(settingsMenuWidget);
0254             settingsPopMenu->addAction(settingsMenuAction);
0255 
0256             btnSettingsMenu->setPopupMode(QToolButton::InstantPopup);
0257             btnSettingsMenu->setMenu(settingsPopMenu);
0258         }
0259 
0260         widgetAreaLayout->addWidget(widget);
0261     }
0262 }
0263 
0264 
0265 
0266 struct KisAnimTimelineDocker::Private
0267 {
0268     Private(QWidget *parent)
0269         : framesModel(new KisAnimTimelineFramesModel(parent))
0270         , framesView(new KisAnimTimelineFramesView(parent))
0271         , titlebar(new KisAnimTimelineDockerTitlebar(parent))
0272         , mainWindow(nullptr)
0273     {
0274         framesView->setModel(framesModel);
0275         framesView->setMinimumHeight(50);
0276 
0277         connect(titlebar->importAudioAction, &QAction::triggered, framesView, &KisAnimTimelineFramesView::slotSelectAudioChannelFile);
0278         connect(titlebar->removeAudioAction, &QAction::triggered, framesView, [&](){framesView->slotAudioChannelRemove();});
0279         connect(titlebar->muteAudioAction, &QAction::triggered, framesView, &KisAnimTimelineFramesView::slotAudioChannelMute);
0280         connect(titlebar->volumeSlider, SIGNAL(valueChanged(int)), framesView, SLOT(slotAudioVolumeChanged(int)));
0281     }
0282 
0283     KisAnimTimelineFramesModel *framesModel;
0284     KisAnimTimelineFramesView *framesView;
0285     KisAnimTimelineDockerTitlebar *titlebar;
0286 
0287     QPointer<KisCanvas2> canvas;
0288     KisPlaybackEngine *playbackEngine {nullptr};
0289 
0290     KisSignalAutoConnectionsStore canvasConnections;
0291     KisMainWindow *mainWindow;
0292     KisAnimationPlaybackControlsModel controlsModel;
0293 };
0294 
0295 
0296 
0297 KisAnimTimelineDocker::KisAnimTimelineDocker()
0298     : QDockWidget(i18n("Animation Timeline"))
0299     , m_d(new Private(this))
0300 {
0301     setWidget(m_d->framesView);
0302 
0303     // Titlebar Widget..
0304     setTitleBarWidget(m_d->titlebar);
0305 
0306     connect(m_d->titlebar->btnOnionSkinsMenu, &QToolButton::released, [this](){
0307         if (m_d->mainWindow) {
0308             QDockWidget *docker = m_d->mainWindow->dockWidget("OnionSkinsDocker");
0309             if (docker) {
0310                 docker->setVisible(!docker->isVisible());
0311             }
0312         }
0313     });
0314 
0315     {
0316         using namespace KisWidgetConnectionUtils;
0317         connectControl(m_d->titlebar->sbSpeed, &m_d->controlsModel, "playbackSpeedDenorm");
0318     }
0319 
0320     // Watch for KisPlaybackEngine changes and initialize current one..
0321     connect(KisPart::instance(), &KisPart::playbackEngineChanged, this, &KisAnimTimelineDocker::setPlaybackEngine);
0322     setPlaybackEngine(KisPart::instance()->playbackEngine());
0323 
0324     setEnabled(false);
0325 }
0326 
0327 KisAnimTimelineDocker::~KisAnimTimelineDocker()
0328 {
0329 }
0330 
0331 struct NodeManagerInterface : KisAnimTimelineFramesModel::NodeManipulationInterface
0332 {
0333     NodeManagerInterface(KisNodeManager *manager) : m_manager(manager) {}
0334 
0335     KisLayerSP addPaintLayer() const override {
0336         return m_manager->createPaintLayer();
0337     }
0338 
0339     void removeNode(KisNodeSP node) const override {
0340         m_manager->removeSingleNode(node);
0341     }
0342 
0343     bool setNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const override
0344     {
0345         return m_manager->trySetNodeProperties(node, image, properties);
0346     }
0347 
0348 private:
0349     KisNodeManager *m_manager;
0350 };
0351 
0352 void KisAnimTimelineDocker::setCanvas(KoCanvasBase * canvas)
0353 {
0354     if (m_d->canvas == canvas) return;
0355 
0356     if (m_d->framesModel->hasConnectionToCanvas()) {
0357         m_d->canvasConnections.clear();
0358         m_d->framesModel->setDummiesFacade(0, 0, 0);
0359         m_d->framesModel->setFrameCache(0);
0360         m_d->framesModel->setAnimationPlayer(0);
0361         m_d->framesModel->setDocument(0);
0362         m_d->framesModel->setNodeManipulationInterface(0);
0363     }
0364 
0365     // Deinitialize from previous canvas...
0366     if (m_d->canvas) { 
0367         m_d->canvas->disconnectCanvasObserver(this);
0368         m_d->canvas->animationState()->disconnect(this);
0369         m_d->titlebar->transport->setPlaying(false);
0370 
0371         m_d->titlebar->btnAudioMenu->setEnabled(false);
0372 
0373         if(m_d->canvas->image()) {
0374             m_d->canvas->image()->animationInterface()->disconnect(this);
0375             m_d->titlebar->sbStartFrame->disconnect(m_d->canvas->image()->animationInterface());
0376             m_d->titlebar->sbEndFrame->disconnect(m_d->canvas->image()->animationInterface());
0377             m_d->titlebar->sbFrameRate->disconnect(m_d->canvas->image()->animationInterface());
0378         }
0379     }
0380 
0381     m_d->canvas = dynamic_cast<KisCanvas2*>(canvas);
0382     setEnabled(m_d->canvas != 0);
0383     m_d->framesView->slotCanvasUpdate(m_d->canvas);
0384 
0385     // Reinitialize new canvas..
0386     if (m_d->canvas) {
0387         KisDocument *doc = static_cast<KisDocument*>(m_d->canvas->imageView()->document());
0388         KisShapeController *kritaShapeController = dynamic_cast<KisShapeController*>(doc->shapeController());
0389         m_d->framesModel->setDummiesFacade(kritaShapeController,
0390                                      m_d->canvas->image(),
0391                                      m_d->canvas->viewManager()->nodeManager()->nodeDisplayModeAdapter());
0392 
0393         m_d->framesModel->setDocument(doc);
0394 
0395         updateFrameCache();
0396 
0397         {   // Titlebar widgets...
0398             KisSignalsBlocker blocker(m_d->titlebar->sbStartFrame,
0399                                       m_d->titlebar->sbEndFrame,
0400                                       m_d->titlebar->sbFrameRate,
0401                                       m_d->titlebar->frameRegister);
0402 
0403             KisImageAnimationInterface *animinterface = m_d->canvas->image()->animationInterface();
0404             m_d->titlebar->sbStartFrame->setValue(animinterface->documentPlaybackRange().start());
0405             m_d->titlebar->sbEndFrame->setValue(animinterface->documentPlaybackRange().end());
0406             m_d->titlebar->sbFrameRate->setValue(animinterface->framerate());
0407             m_d->titlebar->frameRegister->setValue(animinterface->currentTime());
0408             
0409             m_d->titlebar->btnAudioMenu->setEnabled(true); // Menu is disabled until a canvas is loaded.
0410         }
0411 
0412         m_d->framesModel->setAnimationPlayer(m_d->canvas->animationState());
0413 
0414         m_d->framesModel->setNodeManipulationInterface(
0415             new NodeManagerInterface(m_d->canvas->viewManager()->nodeManager()));
0416 
0417         m_d->canvasConnections.addConnection(
0418             m_d->canvas->viewManager()->nodeManager(), SIGNAL(sigNodeActivated(KisNodeSP)),
0419             m_d->framesModel, SLOT(slotCurrentNodeChanged(KisNodeSP)));
0420 
0421         m_d->canvasConnections.addConnection(
0422             m_d->framesModel, SIGNAL(requestCurrentNodeChanged(KisNodeSP)),
0423             m_d->canvas->viewManager()->nodeManager(), SLOT(slotNonUiActivatedNode(KisNodeSP)));
0424 
0425         m_d->framesModel->slotCurrentNodeChanged(m_d->canvas->viewManager()->activeNode());
0426 
0427         m_d->canvasConnections.addConnection(
0428                     m_d->canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()),
0429                     this, SLOT(handleThemeChange()));
0430 
0431         m_d->canvasConnections.addConnection(
0432                     m_d->canvas, SIGNAL(sigCanvasEngineChanged()),
0433                     this, SLOT(updateFrameCache()));
0434 
0435         m_d->titlebar->transport->setPlaying(m_d->canvas->animationState()->playbackState() == PlaybackState::PLAYING);
0436 
0437         m_d->titlebar->volumeSlider->setValue(m_d->framesModel->audioVolume() * 100.0);
0438 
0439         connect(m_d->titlebar->sbFrameRate, SIGNAL(valueChanged(int)), m_d->canvas->image()->animationInterface(), SLOT(setFramerate(int)));
0440         connect(m_d->titlebar->sbStartFrame, SIGNAL(valueChanged(int)), m_d->canvas->image()->animationInterface(), SLOT(setDocumentRangeStartFrame(int)));
0441         connect(m_d->titlebar->sbEndFrame, SIGNAL(valueChanged(int)), m_d->canvas->image()->animationInterface(), SLOT(setDocumentRangeEndFrame(int)));
0442 
0443         connect(m_d->canvas->animationState(), SIGNAL(sigFrameChanged()), this, SLOT(updateFrameRegister()));
0444         connect(m_d->canvas->animationState(), &KisCanvasAnimationState::sigPlaybackStateChanged, this, [this](PlaybackState state){
0445             m_d->titlebar->frameRegister->setDisabled(state == PlaybackState::PLAYING);
0446             if (state == PlaybackState::STOPPED) {
0447                 updateFrameRegister();
0448             }
0449         });
0450         connect(m_d->canvas->animationState(), &KisCanvasAnimationState::sigPlaybackStateChanged, this, [this](PlaybackState state){
0451             m_d->titlebar->transport->setPlaying(state == PlaybackState::PLAYING);
0452         });
0453 
0454         connect(m_d->canvas->animationState(), &KisCanvasAnimationState::sigPlaybackStatisticsUpdated,
0455                 this, &KisAnimTimelineDocker::updatePlaybackStatistics);
0456 
0457         connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigUiTimeChanged(int)), this, SLOT(updateFrameRegister()));
0458 
0459         connect(m_d->canvas->image()->animationInterface(), &KisImageAnimationInterface::sigPlaybackRangeChanged, this, [this]() {
0460             if (!m_d->canvas || !m_d->canvas->image()) return;
0461 
0462             KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface();
0463 
0464             m_d->titlebar->sbStartFrame->setValue(animInterface->documentPlaybackRange().start());
0465             m_d->titlebar->sbEndFrame->setValue(animInterface->documentPlaybackRange().end());
0466         });
0467 
0468         connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()), SLOT(handleFrameRateChange()));
0469 
0470         m_d->controlsModel.connectAnimationState(m_d->canvas->animationState());
0471     }
0472 }
0473 
0474 void KisAnimTimelineDocker::handleThemeChange()
0475 {
0476     if (m_d->framesView) {
0477         m_d->framesView->slotUpdateIcons();
0478     }
0479 }
0480 
0481 void KisAnimTimelineDocker::updateFrameCache()
0482 {
0483     m_d->framesModel->setFrameCache(m_d->canvas->frameCache());
0484 }
0485 
0486 void KisAnimTimelineDocker::updateFrameRegister()
0487 {
0488     if (!m_d->canvas && !m_d->canvas->image()) {
0489         return;
0490     }
0491 
0492     const int frame = m_d->canvas->animationState()->displayProxy()->activeFrame();
0493 
0494     QSignalBlocker blocker(m_d->titlebar->frameRegister);
0495     m_d->titlebar->frameRegister->setValue(frame);
0496 }
0497 
0498 void KisAnimTimelineDocker::updatePlaybackStatistics()
0499 {
0500     qreal effectiveFps = 0.0;
0501     qreal realFps = 0.0;
0502     qreal framesDropped = 0.0;
0503     bool isPlaying = false;
0504 
0505     {
0506         KisPlaybackEngine::PlaybackStats stats = m_d->playbackEngine->playbackStatistics();
0507         effectiveFps = stats.expectedFps;
0508         realFps = stats.realFps;
0509         framesDropped = stats.droppedFramesPortion;
0510         isPlaying = effectiveFps > 0.0;
0511     }
0512 
0513 
0514     KisConfig cfg(true);
0515     const bool shouldDropFrames = cfg.animationDropFrames();
0516 
0517     QAction *action = m_d->titlebar->btnDropFrames->defaultAction();
0518     const bool droppingFrames = framesDropped > 0.05;
0519     action->setIcon(KisIconUtils::loadIcon(droppingFrames ? "droppedframes" : "dropframe"));
0520 
0521     QString actionText;
0522     if (!isPlaying) {
0523         actionText = QString("%1 (%2) \n%3")
0524             .arg(KisAnimUtils::dropFramesActionName)
0525             .arg(KritaUtils::toLocalizedOnOff(shouldDropFrames))
0526             .arg(i18n("Enable to preserve playback timing."));
0527     } else {
0528         actionText = QString("%1 (%2)\n"
0529                        "%3\n"
0530                        "%4\n"
0531                        "%5")
0532             .arg(KisAnimUtils::dropFramesActionName)
0533             .arg(KritaUtils::toLocalizedOnOff(shouldDropFrames))
0534                          .arg(i18n("Effective FPS:\t%1", QString::number(effectiveFps, 'f', 1)))
0535             .arg(i18n("Real FPS:\t%1", QString::number(realFps, 'f', 1)))
0536             .arg(i18n("Frames dropped:\t%1\%", QString::number(framesDropped * 100, 'f', 1)));
0537     }
0538 
0539     /**
0540      * NOTE: we update stats on the **action**, but not on the
0541      * button itself, so the stats will automatically propagate
0542      * to all the buttons that use this action, including the
0543      * one in the curves docker
0544      */
0545     action->setToolTip(actionText);
0546 }
0547 
0548 void KisAnimTimelineDocker::unsetCanvas()
0549 {
0550     setCanvas(0);
0551 }
0552 
0553 void KisAnimTimelineDocker::setViewManager(KisViewManager *view)
0554 {
0555     m_d->mainWindow = view->mainWindow();
0556     KisActionManager *actionManager = view->actionManager();
0557     m_d->framesView->setActionManager(actionManager);
0558 
0559     KisAction *action = 0;
0560 
0561     KisAnimTimelineDockerTitlebar* titleBar = static_cast<KisAnimTimelineDockerTitlebar*>(titleBarWidget());
0562 
0563     action = actionManager->actionByName("add_blank_frame");
0564     titleBar->btnAddKeyframe->setDefaultAction(action);
0565     titleBar->btnAddKeyframe->setIconSize(QSize(22, 22));
0566 
0567     action = actionManager->actionByName("add_duplicate_frame");
0568     titleBar->btnDuplicateKeyframe->setDefaultAction(action);
0569     titleBar->btnDuplicateKeyframe->setIconSize(QSize(22, 22));
0570 
0571     action = actionManager->actionByName("remove_frames");
0572     titleBar->btnRemoveKeyframe->setDefaultAction(action);
0573     titleBar->btnRemoveKeyframe->setIconSize(QSize(22, 22));
0574 
0575     // Connect playback-related actions..
0576     action = actionManager->createAction("toggle_playback");
0577     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0578     connect(action, &KisAction::triggered, this, [this](bool){
0579         m_d->playbackEngine->playPause();
0580     });
0581 
0582     action = actionManager->createAction("stop_playback");
0583     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0584     connect(action, &KisAction::triggered, this, [this](bool){
0585         m_d->playbackEngine->stop();
0586     });
0587 
0588     action = actionManager->createAction("previous_frame");
0589     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0590     connect(action, &KisAction::triggered, this, [this](bool){
0591         m_d->playbackEngine->previousFrame();
0592     });
0593 
0594     action = actionManager->createAction("next_frame");
0595     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0596     connect(action, &KisAction::triggered, this, [this](bool){
0597         m_d->playbackEngine->nextFrame();
0598     });
0599 
0600     action = actionManager->createAction("previous_keyframe");
0601     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0602     connect(action, &KisAction::triggered, this, [this](bool){
0603         m_d->playbackEngine->previousKeyframe();
0604     });
0605 
0606     action = actionManager->createAction("next_keyframe");
0607     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0608     connect(action, &KisAction::triggered, this, [this](bool){
0609         m_d->playbackEngine->nextKeyframe();
0610     });
0611 
0612     action = actionManager->createAction("previous_matching_keyframe");
0613     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0614     connect(action, &KisAction::triggered, this, [this](bool){
0615         m_d->playbackEngine->previousMatchingKeyframe();
0616     });
0617 
0618     action = actionManager->createAction("next_matching_keyframe");
0619     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0620     connect(action, &KisAction::triggered, this, [this](bool){
0621         m_d->playbackEngine->nextMatchingKeyframe();
0622     });
0623 
0624     action = actionManager->createAction("previous_unfiltered_keyframe");
0625     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0626     connect(action, &KisAction::triggered, this, [this](bool){
0627         m_d->playbackEngine->previousUnfilteredKeyframe();
0628     });
0629 
0630     action = actionManager->createAction("next_unfiltered_keyframe");
0631     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0632     connect(action, &KisAction::triggered, this, [this](bool){
0633         m_d->playbackEngine->nextUnfilteredKeyframe();
0634     });
0635 
0636     action = actionManager->createAction("first_frame");
0637     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0638     connect(action, &KisAction::triggered, this, [this](bool){
0639        if (m_d->canvas) {
0640            m_d->playbackEngine->firstFrame();
0641        }
0642     });
0643 
0644     action = actionManager->createAction("last_frame");
0645     action->setActivationFlags(KisAction::ACTIVE_IMAGE);
0646     connect(action, &KisAction::triggered, this, [this](bool){
0647        if (m_d->canvas) {
0648            m_d->playbackEngine->lastFrame();
0649        }
0650     });
0651 
0652     {
0653         action = actionManager->createAction("auto_key");
0654         m_d->titlebar->btnAutoKey->setDefaultAction(action);
0655         m_d->titlebar->btnAutoKey->setIconSize(QSize(22, 22));
0656         connect(action, SIGNAL(triggered(bool)), SLOT(setAutoKey(bool)));
0657 
0658         KisImageConfig config(true);
0659         action->setChecked(config.autoKeyEnabled());
0660         action->setIcon(config.autoKeyEnabled() ? KisIconUtils::loadIcon("auto-key-on") : KisIconUtils::loadIcon("auto-key-off"));
0661 
0662         const bool autoKeyModeDuplicate = config.autoKeyModeDuplicate();
0663         m_d->titlebar->autoKeyBlank->setChecked(!autoKeyModeDuplicate);
0664         m_d->titlebar->autoKeyDuplicate->setChecked(autoKeyModeDuplicate);
0665     }
0666 
0667     {
0668         action = actionManager->createAction("drop_frames");
0669         m_d->titlebar->btnDropFrames->setDefaultAction(action);
0670         m_d->titlebar->btnDropFrames->setIconSize(QSize(22, 22));
0671 
0672         using namespace KisWidgetConnectionUtils;
0673         connectControl(action, &m_d->controlsModel, "dropFramesMode");
0674     }
0675 }
0676 
0677 void KisAnimTimelineDocker::setPlaybackEngine(KisPlaybackEngine *playbackEngine)
0678 {
0679     if (!playbackEngine) return;
0680 
0681     // Connect transport controls..
0682     connect(m_d->titlebar->transport, SIGNAL(skipBack()), playbackEngine, SLOT(previousKeyframe()));
0683     connect(m_d->titlebar->transport, SIGNAL(back()), playbackEngine, SLOT(previousFrame()));
0684     connect(m_d->titlebar->transport, SIGNAL(stop()), playbackEngine, SLOT(stop()));
0685     connect(m_d->titlebar->transport, SIGNAL(playPause()), playbackEngine, SLOT(playPause()));
0686     connect(m_d->titlebar->transport, SIGNAL(forward()), playbackEngine, SLOT(nextFrame()));
0687     connect(m_d->titlebar->transport, SIGNAL(skipForward()), playbackEngine, SLOT(nextKeyframe()));
0688 
0689     connect(m_d->titlebar->frameRegister, SIGNAL(valueChanged(int)), playbackEngine, SLOT(seek(int)));
0690 
0691     m_d->controlsModel.connectPlaybackEngine(playbackEngine);
0692 
0693     m_d->playbackEngine = playbackEngine;
0694 }
0695 
0696 void KisAnimTimelineDocker::setAutoKey(bool value)
0697 {
0698     {
0699         KisImageConfig cfg(false);
0700         if (value != cfg.autoKeyEnabled()) {
0701             cfg.setAutoKeyEnabled(value);
0702             const QIcon icon = cfg.autoKeyEnabled() ? KisIconUtils::loadIcon("auto-key-on") : KisIconUtils::loadIcon("auto-key-off");
0703             QAction* action = m_d->titlebar->btnAutoKey->defaultAction();
0704             action->setIcon(icon);
0705         }
0706     }
0707     KisImageConfigNotifier::instance()->notifyAutoKeyFrameConfigurationChanged();
0708 }
0709 
0710 void KisAnimTimelineDocker::handleFrameRateChange()
0711 {
0712     if (!m_d->canvas || !m_d->canvas->image()) return;
0713 
0714     KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface();
0715 
0716     m_d->titlebar->sbFrameRate->setValue(animInterface->framerate());
0717 }