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

0001 /*
0002  *  SPDX-FileCopyrightText: 2016 Jouni Pentikäinen <joupent@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include <QTreeView>
0008 #include <QSplitter>
0009 #include <QToolBar>
0010 #include <QScroller>
0011 #include <QHBoxLayout>
0012 #include <QFormLayout>
0013 #include <QLabel>
0014 #include <QMenu>
0015 #include <QToolButton>
0016 #include <QSizePolicy>
0017 
0018 #include "KisAnimCurvesDocker.h"
0019 #include "KisAnimCurvesModel.h"
0020 #include "KisAnimCurvesView.h"
0021 #include "KisAnimCurvesChannelsModel.h"
0022 #include "KisAnimCurvesChannelDelegate.h"
0023 
0024 #include "KisCanvasAnimationState.h"
0025 #include "animation/KisFrameDisplayProxy.h"
0026 #include "kis_keyframe_channel.h"
0027 
0028 #include "kis_image_animation_interface.h"
0029 #include "KisAnimUtils.h"
0030 #include "kis_image_config.h"
0031 
0032 #include "KisDocument.h"
0033 #include "kis_canvas2.h"
0034 #include "kis_shape_controller.h"
0035 #include "kis_signal_auto_connection.h"
0036 #include "KisViewManager.h"
0037 #include "kis_node_manager.h"
0038 #include "kis_animation_frame_cache.h"
0039 #include "klocalizedstring.h"
0040 #include "kis_icon_utils.h"
0041 #include "kis_action_manager.h"
0042 #include "kis_action.h"
0043 #include "kis_transport_controls.h"
0044 #include "kis_int_parse_spin_box.h"
0045 #include "kis_slider_spin_box.h"
0046 #include "kis_double_parse_spin_box.h"
0047 #include "kis_zoom_button.h"
0048 #include "kis_collapsible_button_group.h"
0049 #include "kis_signals_blocker.h"
0050 #include "kis_time_span.h"
0051 #include "kis_processing_applicator.h"
0052 #include "KisMainWindow.h"
0053 #include "KisPart.h"
0054 #include "KisPlaybackEngine.h"
0055 #include <QItemSelection>
0056 #include "KisAnimationPlaybackControlsModel.h"
0057 #include "KisWidgetConnectionUtils.h"
0058 
0059 
0060 KisAnimCurvesDockerTitlebar::KisAnimCurvesDockerTitlebar(QWidget* parent) :
0061     KisUtilityTitleBar(new QLabel(i18n("Animation Curves"), parent), parent)
0062 {
0063     // Transport Controls...
0064     transport = new KisTransportControls(this);
0065     widgetAreaLayout->addWidget(transport);
0066     widgetAreaLayout->addSpacing(SPACING_UNIT);
0067 
0068     // Frame Register...
0069     sbFrameRegister = new KisIntParseSpinBox(this);
0070     sbFrameRegister->setToolTip(i18n("Frame register"));
0071     sbFrameRegister->setPrefix("#  ");
0072     sbFrameRegister->setRange(0, MAX_FRAMES);
0073     widgetAreaLayout->addWidget(sbFrameRegister);
0074     widgetAreaLayout->addSpacing(SPACING_UNIT);
0075 
0076     {   // Drop Frames..
0077         btnDropFrames = new QToolButton(this);
0078         btnDropFrames->setAutoRaise(true);
0079         widgetAreaLayout->addWidget(btnDropFrames);
0080 
0081         // Playback Speed..
0082         sbSpeed = new KisSliderSpinBox(this);
0083         sbSpeed->setRange(25, 200);
0084         sbSpeed->setSingleStep(5);
0085         sbSpeed->setValue(100);
0086         sbSpeed->setPrefix(i18nc("preview playback speed percentage prefix", "Speed: "));
0087         sbSpeed->setSuffix(" %");
0088         sbSpeed->setToolTip(i18n("Preview playback speed"));
0089         widgetAreaLayout->addWidget(sbSpeed);
0090     }
0091 
0092     widgetAreaLayout->addSpacing(SPACING_UNIT);
0093 
0094     {   // Frame ops...
0095         QWidget *widget = new QWidget(this);
0096         QHBoxLayout *layout = new QHBoxLayout(widget);
0097         layout->setSpacing(0);
0098         layout->setContentsMargins(0,0,0,0);
0099 
0100         // Add/Remove Key..
0101         btnAddKey = new QToolButton(this);
0102         btnAddKey->setAutoRaise(true);
0103         layout->addWidget(btnAddKey);
0104 
0105         btnRemoveKey = new QToolButton(this);
0106         btnRemoveKey->setAutoRaise(true);
0107         layout->addWidget(btnRemoveKey);
0108 
0109         layout->addSpacing(SPACING_UNIT);
0110 
0111         // Interpolation Modes..
0112         btnGroupInterpolation = new KisCollapsibleButtonGroup(this);
0113         btnGroupInterpolation->setAutoRaise(true);
0114         btnGroupInterpolation->setIconSize(QSize(22, 22));
0115         layout->addWidget(btnGroupInterpolation);
0116 
0117         layout->addSpacing(SPACING_UNIT);
0118 
0119         // Tangent Modes..
0120         btnGroupTangents = new KisCollapsibleButtonGroup(this);
0121         btnGroupTangents->setAutoRaise(true);
0122         btnGroupTangents->setIconSize(QSize(22, 22));
0123         layout->addWidget(btnGroupTangents);
0124 
0125         widgetAreaLayout->addWidget(widget);
0126     }
0127 
0128     widgetAreaLayout->addSpacing(SPACING_UNIT);
0129 
0130     sbValueRegister = new KisDoubleParseSpinBox(this);
0131     sbValueRegister->setPrefix(i18nc("Value (Keep short!)", "Val:"));
0132     sbValueRegister->setRange(-99000.f, 99000.f);
0133     widgetAreaLayout->addWidget(sbValueRegister);
0134 
0135     widgetAreaLayout->addSpacing(SPACING_UNIT);
0136 
0137     // Zoom buttons..
0138     btnGroupZoomFit = new KisCollapsibleButtonGroup(this);
0139     btnGroupZoomFit->setAutoRaise(true);
0140     btnGroupZoomFit->setIconSize(QSize(22,22));
0141     widgetAreaLayout->addWidget(btnGroupZoomFit);
0142 
0143     btnZoomHori = new KisZoomButton(this);
0144     btnZoomHori->setAutoRaise(true);
0145     btnZoomHori->setIcon(KisIconUtils::loadIcon("zoom-horizontal"));
0146     btnZoomHori->setIconSize(QSize(22, 22));
0147     widgetAreaLayout->addWidget(btnZoomHori);
0148 
0149     btnZoomVert = new KisZoomButton(this);
0150     btnZoomVert->setAutoRaise(true);
0151     btnZoomVert->setIcon(KisIconUtils::loadIcon("zoom-vertical"));
0152     btnZoomVert->setIconSize(QSize(22, 22));
0153     widgetAreaLayout->addWidget(btnZoomVert);
0154 
0155     widgetAreaLayout->addStretch();
0156 
0157     {   // Menus..
0158         QWidget *widget = new QWidget(this);
0159 
0160         QHBoxLayout *layout = new QHBoxLayout(widget);
0161         layout->setSpacing(0);
0162         layout->setContentsMargins(SPACING_UNIT,0,0,0);
0163 
0164         // Onion skins menu.
0165         btnOnionSkinsMenu = new QToolButton(this);
0166         btnOnionSkinsMenu->setAutoRaise(true);
0167         btnOnionSkinsMenu->setIcon(KisIconUtils::loadIcon("onion_skin_options"));
0168         btnOnionSkinsMenu->setToolTip(i18n("Onion skins menu"));
0169         btnOnionSkinsMenu->setIconSize(QSize(22, 22));
0170         layout->addWidget(btnOnionSkinsMenu);
0171 
0172         // Audio menu..
0173         btnAudioMenu = new QToolButton(this);
0174         btnAudioMenu->setAutoRaise(true);
0175         btnAudioMenu->setIcon(KisIconUtils::loadIcon("audio-none"));
0176         btnAudioMenu->setToolTip(i18n("Audio menu"));
0177         btnAudioMenu->setIconSize(QSize(22, 22));
0178         btnAudioMenu->hide(); // (NOTE: Hidden for now while audio features develop.)
0179         layout->addWidget(btnAudioMenu);
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(180);
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             settingsMenuLayout->addWidget(fields);
0212             settingsMenuLayout->addWidget(buttons);
0213 
0214             layout->addWidget(btnSettingsMenu);
0215 
0216             QMenu *settingsPopMenu = new QMenu(this);
0217             QWidgetAction *settingsMenuAction = new QWidgetAction(this);
0218             settingsMenuAction->setDefaultWidget(settingsMenuWidget);
0219             settingsPopMenu->addAction(settingsMenuAction);
0220 
0221             btnSettingsMenu->setPopupMode(QToolButton::InstantPopup);
0222             btnSettingsMenu->setMenu(settingsPopMenu);
0223         }
0224 
0225         widgetAreaLayout->addWidget(widget);
0226     }
0227 }
0228 
0229 struct KisAnimCurvesDocker::Private
0230 {
0231     Private(QWidget *parent)
0232         : titlebar(new KisAnimCurvesDockerTitlebar(parent))
0233         , curvesModel(new KisAnimCurvesModel(parent))
0234         , curvesView(new KisAnimCurvesView(parent))
0235         , channelTreeModel(new KisAnimCurvesChannelsModel(curvesModel, parent))
0236         , channelTreeView(new QTreeView(parent))
0237         , channelTreeMenuChannels(new QMenu(parent))
0238         , channelTreeMenuLayers(new QMenu(parent))
0239         , mainWindow(nullptr)
0240     {
0241     }
0242 
0243     KisAnimCurvesDockerTitlebar *titlebar;
0244 
0245     KisAnimCurvesModel *curvesModel;
0246     KisAnimCurvesView *curvesView;
0247 
0248     KisAnimCurvesChannelsModel *channelTreeModel;
0249     QTreeView *channelTreeView;
0250 
0251     QMenu *channelTreeMenuChannels; //Menu for channels
0252     QMenu *channelTreeMenuLayers; //Menu for layers
0253 
0254     KisMainWindow *mainWindow;
0255     QPointer<KisCanvas2> canvas;
0256     KisSignalAutoConnectionsStore canvasConnections;
0257     KisAnimationPlaybackControlsModel controlsModel;
0258 };
0259 
0260 KisAnimCurvesDocker::KisAnimCurvesDocker()
0261     : QDockWidget(i18n("Animation Curves"))
0262     , m_d(new Private(this))
0263 {
0264     QWidget *mainWidget = new QWidget(0);
0265     mainWidget->setLayout(new QVBoxLayout());
0266     setWidget(mainWidget);
0267 
0268     QSplitter *mainSplitter = new QSplitter(this);
0269     mainWidget->layout()->addWidget(mainSplitter);
0270 
0271     {   // Channel Tree..
0272         m_d->channelTreeView->setModel(m_d->channelTreeModel);
0273         m_d->channelTreeView->setHeaderHidden(true);
0274         KisAnimCurvesChannelDelegate *listDelegate = new KisAnimCurvesChannelDelegate(this);
0275         m_d->channelTreeView->setItemDelegate(listDelegate);
0276 
0277         //Right click menu configuration for Channel Tree
0278         m_d->channelTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
0279         connect(m_d->channelTreeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(requestChannelMenuAt(QPoint)));
0280 
0281         m_d->channelTreeMenuChannels->addSection(i18n("Channel Operations"));
0282         m_d->channelTreeMenuLayers->addSection(i18n("Layer Operations"));
0283 
0284         { //Channels Menu
0285             QAction* action = new QAction(i18n("Reset Channel"), this);
0286             connect(action, SIGNAL(triggered(bool)), this, SLOT(resetChannelTreeSelection()));
0287             m_d->channelTreeMenuChannels->addAction(action);
0288         }
0289 
0290         { //Layers Menu
0291             QAction* action = new QAction(i18n("Reset All Channels"), this);
0292             connect(action, SIGNAL(triggered(bool)), this, SLOT(resetChannelTreeSelection()));
0293             m_d->channelTreeMenuLayers->addAction(action);
0294         }
0295     }
0296 
0297     {   // Curves View..
0298         m_d->curvesView->setModel(m_d->curvesModel);
0299     }
0300 
0301     mainSplitter->addWidget(m_d->channelTreeView);
0302     mainSplitter->setStretchFactor(0, 1);
0303     mainSplitter->addWidget(m_d->curvesView);
0304     mainSplitter->setStretchFactor(1, 10);
0305 
0306     // Kinetic Scrolling..
0307     QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(m_d->channelTreeView);
0308     if (scroller){
0309         connect(scroller, SIGNAL(stateChanged(QScroller::State)),
0310                 this, SLOT(slotScrollerStateChanged(QScroller::State)));
0311     }
0312 
0313     connect(m_d->channelTreeModel, &KisAnimCurvesChannelsModel::rowsInserted,
0314             this, &KisAnimCurvesDocker::slotListRowsInserted);
0315 
0316     // Titlebar Widget..
0317     setTitleBarWidget(m_d->titlebar);
0318     setEnabled(false);
0319 
0320     connect(m_d->titlebar->btnOnionSkinsMenu, &QToolButton::released, [this](){
0321         if (m_d->mainWindow) {
0322             QDockWidget *docker = m_d->mainWindow->dockWidget("OnionSkinsDocker");
0323             if (docker) {
0324                 docker->setVisible(!docker->isVisible());
0325             }
0326         }
0327     });
0328 
0329     connect(m_d->titlebar->btnZoomHori, &KisZoomButton::zoom, [this](qreal zoomDelta){
0330         if (m_d->curvesView) {
0331             m_d->curvesView->changeZoom(Qt::Horizontal, zoomDelta);
0332         }
0333     });
0334 
0335     connect(m_d->titlebar->btnZoomVert, &KisZoomButton::zoom, [this](qreal zoomDelta){
0336         if (m_d->curvesView) {
0337             m_d->curvesView->changeZoom(Qt::Vertical, zoomDelta);
0338         }
0339     });
0340 
0341     connect(m_d->curvesView, SIGNAL(activated(QModelIndex)), this, SLOT(slotActiveNodeUpdate(QModelIndex)));
0342     connect(m_d->curvesView, SIGNAL(activeDataChanged(QModelIndex)), this, SLOT(slotActiveNodeUpdate(QModelIndex)));
0343     connect(m_d->titlebar->sbValueRegister, SIGNAL(valueChanged(double)), this, SLOT(slotValueRegisterChanged(double)));
0344 
0345     {
0346         using namespace KisWidgetConnectionUtils;
0347         connectControl(m_d->titlebar->sbSpeed, &m_d->controlsModel, "playbackSpeedDenorm");
0348     }
0349 
0350     // Watch for KisPlaybackEngine changes and initialize current one..
0351     connect(KisPart::instance(), &KisPart::playbackEngineChanged, this, &KisAnimCurvesDocker::setPlaybackEngine);
0352     setPlaybackEngine(KisPart::instance()->playbackEngine());
0353 }
0354 
0355 KisAnimCurvesDocker::~KisAnimCurvesDocker()
0356 {}
0357 
0358 void KisAnimCurvesDocker::setCanvas(KoCanvasBase *canvas)
0359 {
0360     if (canvas && m_d->canvas == canvas) return;
0361 
0362     if (m_d->canvas) {
0363         m_d->canvasConnections.clear();
0364         m_d->canvas->disconnectCanvasObserver(this);
0365         m_d->channelTreeModel->selectedNodesChanged(KisNodeList());
0366         m_d->canvas->animationState()->disconnect(this);
0367         m_d->titlebar->transport->disconnect(m_d->canvas->animationState());
0368         m_d->titlebar->transport->setPlaying(false);
0369         m_d->titlebar->sbFrameRegister->disconnect(m_d->canvas->animationState());
0370 
0371         if (m_d->canvas->image()) {
0372             m_d->canvas->image()->animationInterface()->disconnect(this);
0373             m_d->titlebar->sbStartFrame->disconnect(m_d->canvas->image()->animationInterface());
0374             m_d->titlebar->sbEndFrame->disconnect(m_d->canvas->image()->animationInterface());
0375             m_d->titlebar->sbFrameRate->disconnect(m_d->canvas->image()->animationInterface());
0376         }
0377 
0378         m_d->curvesModel->setImage(0);
0379     }
0380 
0381     m_d->canvas = dynamic_cast<KisCanvas2*>(canvas);
0382     setEnabled(m_d->canvas != 0);
0383 
0384     if (m_d->canvas) {
0385         KisDocument *doc = static_cast<KisDocument*>(m_d->canvas->imageView()->document());
0386         KisShapeController *kritaShapeController = dynamic_cast<KisShapeController*>(doc->shapeController());
0387         m_d->channelTreeModel->setDummiesFacade(kritaShapeController);
0388 
0389         m_d->curvesModel->setImage(m_d->canvas->image());
0390         m_d->curvesModel->setFrameCache(m_d->canvas->frameCache());
0391         m_d->curvesModel->setAnimationPlayer(m_d->canvas->animationState());
0392 
0393         m_d->canvasConnections.addConnection(
0394             m_d->canvas->viewManager()->nodeManager(), SIGNAL(sigUiNeedChangeSelectedNodes(KisNodeList)),
0395             m_d->channelTreeModel, SLOT(selectedNodesChanged(KisNodeList))
0396         );
0397 
0398         m_d->canvasConnections.addConnection(
0399             m_d->canvas->viewManager()->nodeManager(), SIGNAL(sigNodeActivated(KisNodeSP)),
0400             this, SLOT(slotNodeActivated(KisNodeSP))
0401         );
0402 
0403         m_d->channelTreeModel->clear();
0404         m_d->channelTreeModel->selectedNodesChanged(m_d->canvas->viewManager()->nodeManager()->selectedNodes());
0405 
0406         {   // Reinitialize titlebar widgets..
0407             KisSignalsBlocker blocker(m_d->titlebar->sbStartFrame,
0408                                       m_d->titlebar->sbEndFrame,
0409                                       m_d->titlebar->sbFrameRate,
0410                                       m_d->titlebar->sbFrameRegister,
0411                                       m_d->titlebar->sbValueRegister);
0412 
0413             KisImageAnimationInterface *animinterface = m_d->canvas->image()->animationInterface();
0414             m_d->titlebar->sbStartFrame->setValue(animinterface->documentPlaybackRange().start());
0415             m_d->titlebar->sbEndFrame->setValue(animinterface->documentPlaybackRange().end());
0416             m_d->titlebar->sbFrameRate->setValue(animinterface->framerate());
0417             m_d->titlebar->sbFrameRegister->setValue(animinterface->currentTime());
0418 
0419             QModelIndex activeIndex = m_d->curvesView->currentIndex();
0420             m_d->titlebar->sbValueRegister->setEnabled(activeIndex.isValid());
0421             m_d->titlebar->sbValueRegister->setValue( activeIndex.isValid() ?
0422                                                       activeIndex.data(KisAnimCurvesModel::ScalarValueRole).toReal() : 0.0f);
0423         }
0424 
0425         m_d->titlebar->transport->setPlaying(m_d->canvas->animationState()->playbackState() == PlaybackState::PLAYING);
0426 
0427         connect(m_d->titlebar->sbFrameRate, SIGNAL(valueChanged(int)), m_d->canvas->image()->animationInterface(), SLOT(setFramerate(int)));
0428         connect(m_d->titlebar->sbStartFrame, SIGNAL(valueChanged(int)), m_d->canvas->image()->animationInterface(), SLOT(setDocumentRangeStartFrame(int)));
0429         connect(m_d->titlebar->sbEndFrame, SIGNAL(valueChanged(int)), m_d->canvas->image()->animationInterface(), SLOT(setDocumentRangeEndFrame(int)));
0430 
0431         connect(m_d->canvas->animationState(), &KisCanvasAnimationState::sigPlaybackStateChanged, m_d->titlebar->transport, [this](PlaybackState p_state){
0432             m_d->titlebar->transport->setPlaying(p_state == PlaybackState::PLAYING);
0433             m_d->titlebar->sbFrameRegister->setDisabled(p_state == PlaybackState::PLAYING);
0434 
0435             if (p_state == PlaybackState::STOPPED) {
0436                 updateFrameRegister();
0437             }
0438         });
0439 
0440         connect(m_d->canvas->animationState(), SIGNAL(sigFrameChanged()), this, SLOT(updateFrameRegister()));
0441 
0442         connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigUiTimeChanged(int)), this, SLOT(updateFrameRegister()));
0443 
0444         connect(m_d->canvas->image()->animationInterface(), &KisImageAnimationInterface::sigPlaybackRangeChanged, this, [this]() {
0445             if (!m_d->canvas || !m_d->canvas->image()) return;
0446 
0447             KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface();
0448 
0449             m_d->titlebar->sbStartFrame->setValue(animInterface->documentPlaybackRange().start());
0450             m_d->titlebar->sbEndFrame->setValue(animInterface->documentPlaybackRange().end());
0451         });
0452 
0453         connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()), this, SLOT(handleFrameRateChange()));
0454 
0455         m_d->controlsModel.connectAnimationState(m_d->canvas->animationState());
0456     }
0457 }
0458 
0459 void KisAnimCurvesDocker::unsetCanvas()
0460 {
0461     setCanvas(0);
0462 }
0463 
0464 void KisAnimCurvesDocker::setViewManager(KisViewManager *view)
0465 {
0466     m_d->mainWindow = view->mainWindow();
0467 
0468     connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons()));
0469     slotUpdateIcons();
0470 
0471     KisActionManager* actionManager = view->actionManager();
0472 
0473     KisAction* action = actionManager->createAction("add_scalar_keyframes");
0474     action->setIcon(KisIconUtils::loadIcon("keyframe-add"));
0475     connect(action, SIGNAL(triggered(bool)),
0476             this, SLOT(slotAddAllEnabledKeys()));
0477     m_d->titlebar->btnAddKey->setDefaultAction(action);
0478     m_d->titlebar->btnAddKey->setIconSize(QSize(22, 22));
0479 
0480     action = actionManager->createAction("remove_scalar_keyframe");
0481     action->setIcon(KisIconUtils::loadIcon("keyframe-remove"));
0482     connect(action, SIGNAL(triggered(bool)),
0483             this, SLOT(slotRemoveSelectedKeys()));
0484     m_d->titlebar->btnRemoveKey->setDefaultAction(action);
0485     m_d->titlebar->btnRemoveKey->setIconSize(QSize(22, 22));
0486 
0487     action = actionManager->createAction("interpolation_constant");
0488     action->setIcon(KisIconUtils::loadIcon("interpolation_constant"));
0489     action->setToolTip(i18n("Hold constant value. No interpolation."));
0490     connect(action, &KisAction::triggered,
0491             m_d->curvesView, &KisAnimCurvesView::applyConstantMode);
0492     m_d->titlebar->btnGroupInterpolation->addAction(action);
0493 
0494     action = actionManager->createAction("interpolation_linear");
0495     action->setIcon(KisIconUtils::loadIcon("interpolation_linear"));
0496     action->setToolTip(i18n("Linear interpolation."));
0497     connect(action, &KisAction::triggered,
0498             m_d->curvesView, &KisAnimCurvesView::applyLinearMode);
0499     m_d->titlebar->btnGroupInterpolation->addAction(action);
0500 
0501     action = actionManager->createAction("interpolation_bezier");
0502     action->setIcon(KisIconUtils::loadIcon("interpolation_bezier"));
0503     action->setToolTip(i18n("Bezier curve interpolation."));
0504     connect(action, &KisAction::triggered,
0505             m_d->curvesView, &KisAnimCurvesView::applyBezierMode);
0506     m_d->titlebar->btnGroupInterpolation->addAction(action);
0507 
0508     action = actionManager->createAction("tangents_sharp");
0509     action->setIcon(KisIconUtils::loadIcon("interpolation_sharp"));
0510     action->setToolTip(i18n("Sharp interpolation tangents."));
0511     connect(action, &KisAction::triggered,
0512             m_d->curvesView, &KisAnimCurvesView::applySharpMode);
0513     m_d->titlebar->btnGroupTangents->addAction(action);
0514 
0515     action = actionManager->createAction("tangents_smooth");
0516     action->setIcon(KisIconUtils::loadIcon("interpolation_smooth"));
0517     action->setToolTip(i18n("Smooth interpolation tangents."));
0518     connect(action, &KisAction::triggered,
0519             m_d->curvesView, &KisAnimCurvesView::applySmoothMode);
0520     m_d->titlebar->btnGroupTangents->addAction(action);
0521 
0522     action = actionManager->createAction("zoom_to_fit_range");
0523     action->setIcon(KisIconUtils::loadIcon("zoom-fit"));
0524     action->setToolTip(i18n("Zoom view to fit channel range."));
0525     connect(action, &KisAction::triggered,
0526             m_d->curvesView, &KisAnimCurvesView::zoomToFitChannel);
0527     m_d->titlebar->btnGroupZoomFit->addAction(action);
0528 
0529     action = actionManager->createAction("zoom_to_fit_curve");
0530     action->setIcon(KisIconUtils::loadIcon("zoom-fit-curve"));
0531     action->setToolTip(i18n("Zoom view to fit curve."));
0532     connect(action, &KisAction::triggered,
0533             m_d->curvesView, &KisAnimCurvesView::zoomToFitCurve);
0534     m_d->titlebar->btnGroupZoomFit->addAction(action);
0535 
0536     {
0537         action = actionManager->createAction("drop_frames");
0538         m_d->titlebar->btnDropFrames->setDefaultAction(action);
0539         m_d->titlebar->btnDropFrames->setIconSize(QSize(22, 22));
0540 
0541         using namespace KisWidgetConnectionUtils;
0542         connectControl(action, &m_d->controlsModel, "dropFramesMode");
0543     }
0544 }
0545 
0546 void KisAnimCurvesDocker::setPlaybackEngine(KisPlaybackEngine *playbackEngine)
0547 {
0548     if (!playbackEngine) return;
0549 
0550     // Connect transport controls..
0551     connect(m_d->titlebar->transport, SIGNAL(skipBack()), playbackEngine, SLOT(previousKeyframe()));
0552     connect(m_d->titlebar->transport, SIGNAL(back()), playbackEngine, SLOT(previousFrame()));
0553     connect(m_d->titlebar->transport, SIGNAL(stop()), playbackEngine, SLOT(stop()));
0554     connect(m_d->titlebar->transport, SIGNAL(playPause()), playbackEngine, SLOT(playPause()));
0555     connect(m_d->titlebar->transport, SIGNAL(forward()), playbackEngine, SLOT(nextFrame()));
0556     connect(m_d->titlebar->transport, SIGNAL(skipForward()), playbackEngine, SLOT(nextKeyframe()));
0557 
0558     connect(m_d->titlebar->sbFrameRegister, SIGNAL(valueChanged(int)), playbackEngine, SLOT(seek(int)));
0559 
0560     m_d->controlsModel.connectPlaybackEngine(playbackEngine);
0561 }
0562 
0563 void KisAnimCurvesDocker::addKeyframeCommandToParent(const QString &channelIdentity, KUndo2Command* parentCMD)
0564 {
0565     if (!m_d->canvas) return;
0566 
0567     KisNodeSP node = m_d->canvas->viewManager()->activeNode();
0568     if (!node) return;
0569 
0570     const int time = m_d->canvas->image()->animationInterface()->currentTime();
0571     KisAnimUtils::createKeyframeCommand(m_d->canvas->image(), node, channelIdentity, time, false, parentCMD);
0572 }
0573 
0574 void KisAnimCurvesDocker::addKeyframeQuick(const QString &channelIdentity)
0575 {
0576     if (!m_d->canvas) return;
0577 
0578     KisNodeSP node = m_d->canvas->viewManager()->activeNode();
0579     if (!node) return;
0580 
0581     const int time = m_d->canvas->image()->animationInterface()->currentTime();
0582     KisAnimUtils::createKeyframeLazy(m_d->canvas->image(), node, channelIdentity, time, false);
0583 }
0584 
0585 void KisAnimCurvesDocker::removeKeyframe(const QString &channel)
0586 {
0587     if (!m_d->canvas) return;
0588 
0589     KisNodeSP node = m_d->canvas->viewManager()->activeNode();
0590     if (!node) return;
0591 
0592     QItemSelectionModel* selectionModel = m_d->curvesView->selectionModel();
0593     QModelIndexList selected = selectionModel ? selectionModel->selectedIndexes() : QModelIndexList();
0594 
0595     if (selected.count() > 0) {
0596         Q_FOREACH(const QModelIndex& selection, selected) {
0597             KisAnimUtils::removeKeyframe(m_d->canvas->image(), node, channel, selection.column());
0598         }
0599     } else {
0600         const int time = m_d->canvas->image()->animationInterface()->currentTime();
0601         KisAnimUtils::removeKeyframe(m_d->canvas->image(), node, channel, time);
0602     }
0603 }
0604 
0605 void KisAnimCurvesDocker::slotScrollerStateChanged(QScroller::State state)
0606 {
0607     KisKineticScroller::updateCursor(m_d->channelTreeView, state);
0608 }
0609 
0610 void KisAnimCurvesDocker::slotNodeActivated(KisNodeSP node)
0611 {
0612     if (!node) return;
0613     bool supported = node->supportsKeyframeChannel(KisKeyframeChannel::Opacity.id()) ||
0614             node->supportsKeyframeChannel(KisKeyframeChannel::PositionX.id()) ||
0615             node->supportsKeyframeChannel(KisKeyframeChannel::PositionY.id()) ||
0616             node->supportsKeyframeChannel(KisKeyframeChannel::ScaleX.id()) ||
0617             node->supportsKeyframeChannel(KisKeyframeChannel::ScaleY.id()) ||
0618             node->supportsKeyframeChannel(KisKeyframeChannel::ShearX.id()) ||
0619             node->supportsKeyframeChannel(KisKeyframeChannel::ShearY.id()) ||
0620             node->supportsKeyframeChannel(KisKeyframeChannel::RotationX.id()) ||
0621             node->supportsKeyframeChannel(KisKeyframeChannel::RotationY.id()) ||
0622             node->supportsKeyframeChannel(KisKeyframeChannel::RotationZ.id());
0623     m_d->titlebar->btnAddKey->setEnabled(supported);
0624 }
0625 
0626 void KisAnimCurvesDocker::updateFrameRegister(){
0627     if (!m_d->canvas && !m_d->canvas->image()) {
0628         return;
0629     }
0630 
0631     const int frame = m_d->canvas->animationState()->displayProxy()->activeFrame();
0632 
0633     QSignalBlocker blocker(m_d->titlebar->sbFrameRegister);
0634     m_d->titlebar->sbFrameRegister->setValue(frame);
0635 }
0636 
0637 void KisAnimCurvesDocker::handleFrameRateChange()
0638 {
0639     if (!m_d->canvas || !m_d->canvas->image()) return;
0640 
0641     KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface();
0642     m_d->titlebar->sbFrameRate->setValue(animInterface->framerate());
0643 }
0644 
0645 void KisAnimCurvesDocker::slotUpdateIcons()
0646 {
0647 }
0648 
0649 void KisAnimCurvesDocker::slotAddAllEnabledKeys()
0650 {
0651     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->canvas && m_d->canvas->viewManager());
0652     // remember current node's opacity and set it once we create a new opacity keyframe
0653     KisNodeSP node = m_d->canvas->viewManager()->activeNode();
0654     KIS_SAFE_ASSERT_RECOVER_RETURN(node);
0655 
0656     /* Once we start dealing with more scalar values,
0657      * we should add a dropdown check-box set of actions that can
0658      * enable and disable keys. For now, we will just consider all channels
0659      * enabled. */
0660     KUndo2Command* parentCMD = new KUndo2Command(kundo2_i18n("Add Scalar Keyframes"));
0661 
0662     //This should eventually be a list of all currently enabled channels.
0663     QList<KoID> ids = {
0664         KisKeyframeChannel::Opacity,
0665         KisKeyframeChannel::PositionX,
0666         KisKeyframeChannel::PositionY,
0667         KisKeyframeChannel::ScaleX,
0668         KisKeyframeChannel::ScaleY,
0669         KisKeyframeChannel::ShearX,
0670         KisKeyframeChannel::ShearY,
0671         KisKeyframeChannel::RotationX,
0672         KisKeyframeChannel::RotationY,
0673         KisKeyframeChannel::RotationZ
0674     };
0675 
0676     Q_FOREACH( const KoID& koid, ids ) {
0677         if (node->supportsKeyframeChannel(koid.id())) {
0678             addKeyframeCommandToParent(koid.id(), parentCMD);
0679         }
0680     }
0681 
0682     if (m_d->canvas && m_d->canvas->image()) {
0683         KisProcessingApplicator::runSingleCommandStroke(m_d->canvas->image(), parentCMD,
0684                                                         KisStrokeJobData::BARRIER,
0685                                                         KisStrokeJobData::EXCLUSIVE);
0686     }
0687 }
0688 
0689 void KisAnimCurvesDocker::slotAddOpacityKey()
0690 {
0691     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->canvas && m_d->canvas->viewManager());
0692 
0693     KisNodeSP node = m_d->canvas->viewManager()->activeNode();
0694     KIS_SAFE_ASSERT_RECOVER_RETURN(node);
0695 
0696     if (node->supportsKeyframeChannel(KisKeyframeChannel::Opacity.id())) {
0697         addKeyframeQuick(KisKeyframeChannel::Opacity.id());
0698     }
0699 }
0700 
0701 
0702 void KisAnimCurvesDocker::slotRemoveSelectedKeys()
0703 {
0704     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->canvas && m_d->canvas->viewManager());
0705 
0706     KisNodeSP node = m_d->canvas->viewManager()->activeNode();
0707     KIS_SAFE_ASSERT_RECOVER_RETURN(node);
0708 
0709 
0710     QItemSelectionModel* selectionModel = m_d->curvesView->selectionModel();
0711     QModelIndexList selected = selectionModel ? selectionModel->selectedIndexes() : QModelIndexList();
0712 
0713     QVector<KisAnimUtils::FrameItem> framesToRemove;
0714 
0715     if (selected.count() > 0) {
0716         Q_FOREACH(const QModelIndex& selection, selected) {
0717             QVariant data = selection.data(KisAnimCurvesModel::ChannelIdentifier);
0718 
0719             if (!data.isValid())
0720                 continue;
0721 
0722             const QString identifier = data.toString();
0723             const int time = selection.column();
0724             framesToRemove.push_back(KisAnimUtils::FrameItem(node, identifier, time));
0725         }
0726     } else {
0727         const int time = m_d->canvas->image()->animationInterface()->currentTime();
0728         for(int channelIndex = 0; channelIndex < m_d->curvesModel->rowCount(); channelIndex++) {
0729             QModelIndex chanIndex = m_d->curvesModel->index(channelIndex, time);
0730             if (!chanIndex.isValid())
0731                 continue;
0732 
0733             QVariant data = chanIndex.data(KisAnimCurvesModel::ChannelIdentifier);
0734             if (!data.isValid())
0735                 continue;
0736 
0737             const QString identifier = data.toString();
0738             framesToRemove.push_back(KisAnimUtils::FrameItem(node, identifier, time));
0739         }
0740     }
0741 
0742     if (m_d->canvas && m_d->canvas->image()) {
0743         KisAnimUtils::removeKeyframes(m_d->canvas->image(), framesToRemove);
0744     }
0745 }
0746 
0747 void KisAnimCurvesDocker::slotRemoveOpacityKey()
0748 {
0749     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->canvas && m_d->canvas->viewManager());
0750 
0751     KisNodeSP node = m_d->canvas->viewManager()->activeNode();
0752     KIS_SAFE_ASSERT_RECOVER_RETURN(node);
0753 
0754     if (node->supportsKeyframeChannel(KisKeyframeChannel::Opacity.id())) {
0755         removeKeyframe(KisKeyframeChannel::Opacity.id());
0756     }
0757 }
0758 
0759 void KisAnimCurvesDocker::slotListRowsInserted(const QModelIndex &parentIndex, int first, int last)
0760 {
0761     // Auto-expand nodes on the tree
0762     for (int r=first; r<=last; r++) {
0763         QModelIndex index = m_d->channelTreeModel->index(r, 0, parentIndex);
0764         m_d->channelTreeView->expand(index);
0765     }
0766 }
0767 
0768 void KisAnimCurvesDocker::slotValueRegisterChanged(double value){
0769     if (!m_d->curvesModel)
0770         return;
0771 
0772     QModelIndex current = m_d->curvesView->currentIndex();
0773 
0774     if (current.isValid() && m_d->curvesView->indexHasKey(current)) {
0775         m_d->curvesModel->setData(current, value, KisAnimCurvesModel::ScalarValueRole);
0776     }
0777 }
0778 
0779 void KisAnimCurvesDocker::slotActiveNodeUpdate(const QModelIndex index)
0780 {
0781     KisSignalsBlocker blockSignal(m_d->titlebar->sbValueRegister);
0782 
0783     if (index.isValid() && m_d->curvesView->indexHasKey(index)) {
0784         QVariant variant = m_d->curvesModel->data(index, KisAnimCurvesModel::ScalarValueRole);
0785         m_d->titlebar->sbValueRegister->setEnabled(variant.isValid());
0786         m_d->titlebar->sbValueRegister->setValue(variant.isValid() ? variant.toReal() : 0.0);
0787     } else {
0788         m_d->titlebar->sbValueRegister->setEnabled(false);
0789     }
0790 }
0791 
0792 void KisAnimCurvesDocker::requestChannelMenuAt(const QPoint &point)
0793 {
0794     if (m_d->channelTreeView->selectionModel()->selectedIndexes().size() == 0) {
0795         return;
0796     }
0797     QModelIndex selected = m_d->channelTreeView->selectionModel()->selectedIndexes().first();
0798 
0799     if (selected.data(KisAnimCurvesChannelsModel::CurveRole).toBool()) {
0800         m_d->channelTreeMenuChannels->popup(m_d->channelTreeView->mapToGlobal(point));
0801     } else {
0802         m_d->channelTreeMenuLayers->popup(m_d->channelTreeView->mapToGlobal(point));
0803     }
0804 }
0805 
0806 void KisAnimCurvesDocker::resetChannelTreeSelection()
0807 {
0808     QList<QModelIndex> selected = m_d->channelTreeView->selectionModel()->selectedIndexes();
0809     Q_FOREACH( const QModelIndex& index, selected) {
0810         m_d->channelTreeModel->reset(index);
0811     }
0812 }