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 }