File indexing completed on 2024-05-05 04:54:13

0001 /*
0002     SPDX-FileCopyrightText: 2017 Nicolas Carion
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "timelinetabs.hpp"
0007 #include "assets/model/assetparametermodel.hpp"
0008 #include "audiomixer/mixermanager.hpp"
0009 #include "bin/projectclip.h"
0010 #include "bin/projectitemmodel.h"
0011 #include "core.h"
0012 #include "doc/docundostack.hpp"
0013 #include "doc/kdenlivedoc.h"
0014 #include "mainwindow.h"
0015 #include "monitor/monitor.h"
0016 #include "monitor/monitormanager.h"
0017 #include "monitor/monitorproxy.h"
0018 #include "project/projectmanager.h"
0019 #include "timelinecontroller.h"
0020 #include "timelinewidget.h"
0021 
0022 #include <KMessageBox>
0023 #include <KXMLGUIFactory>
0024 #include <QInputDialog>
0025 #include <QMenu>
0026 #include <QQmlContext>
0027 
0028 TimelineContainer::TimelineContainer(QWidget *parent)
0029     : QWidget(parent)
0030 {
0031 }
0032 
0033 QSize TimelineContainer::sizeHint() const
0034 {
0035     return QSize(800, pCore->window()->height() / 2);
0036 }
0037 
0038 TimelineTabs::TimelineTabs(QWidget *parent)
0039     : QTabWidget(parent)
0040     , m_activeTimeline(nullptr)
0041 {
0042     setTabBarAutoHide(true);
0043     setTabsClosable(false);
0044     setDocumentMode(true);
0045     setMovable(true);
0046     QToolButton *pb = new QToolButton(this);
0047     pb->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0048     pb->setAutoRaise(true);
0049     pb->setToolTip(i18n("Add Timeline Sequence"));
0050     pb->setWhatsThis(
0051         i18n("Add Timeline Sequence. This will create a new timeline for editing. Each timeline corresponds to a Sequence Clip in the Project Bin"));
0052     connect(pb, &QToolButton::clicked, [=]() { pCore->triggerAction(QStringLiteral("add_playlist_clip")); });
0053     setCornerWidget(pb);
0054     connect(this, &TimelineTabs::currentChanged, this, &TimelineTabs::connectCurrent);
0055     connect(this, &TimelineTabs::tabCloseRequested, this, &TimelineTabs::closeTimelineByIndex);
0056     connect(tabBar(), &QTabBar::tabBarDoubleClicked, this, &TimelineTabs::onTabBarDoubleClicked);
0057 }
0058 
0059 TimelineTabs::~TimelineTabs()
0060 {
0061     // clear source
0062     for (int i = 0; i < count(); i++) {
0063         TimelineWidget *timeline = static_cast<TimelineWidget *>(widget(i));
0064         timeline->setSource(QUrl());
0065     };
0066 }
0067 
0068 void TimelineTabs::updateWindowTitle()
0069 {
0070     // Show current timeline name in Window title if we have multiple sequences but only one opened
0071     if (m_activeTimeline == nullptr || pCore->currentDoc()->closing) {
0072         return;
0073     }
0074     if (count() == 1 && pCore->projectItemModel()->sequenceCount() > 1) {
0075         pCore->window()->setWindowTitle(pCore->currentDoc()->description(KLocalizedString::removeAcceleratorMarker(tabText(0))));
0076         m_activeTimeline->model()->updateVisibleSequenceName(tabText(0));
0077     } else {
0078         pCore->window()->setWindowTitle(pCore->currentDoc()->description());
0079         m_activeTimeline->model()->updateVisibleSequenceName(QString());
0080     }
0081 }
0082 
0083 bool TimelineTabs::raiseTimeline(const QUuid &uuid)
0084 {
0085     QMutexLocker lk(&m_lock);
0086     for (int i = 0; i < count(); i++) {
0087         TimelineWidget *timeline = static_cast<TimelineWidget *>(widget(i));
0088         if (timeline->getUuid() == uuid) {
0089             if (i != currentIndex()) {
0090                 setCurrentIndex(i);
0091             }
0092             return true;
0093         }
0094     }
0095     return false;
0096 }
0097 
0098 void TimelineTabs::setModified(const QUuid &uuid, bool modified)
0099 {
0100     for (int i = 0; i < count(); i++) {
0101         TimelineWidget *timeline = static_cast<TimelineWidget *>(widget(i));
0102         if (timeline->getUuid() == uuid) {
0103             setTabIcon(i, modified ? QIcon::fromTheme(QStringLiteral("document-save")) : QIcon());
0104             break;
0105         }
0106     }
0107 }
0108 
0109 TimelineWidget *TimelineTabs::addTimeline(const QUuid uuid, const QString &tabName, std::shared_ptr<TimelineItemModel> timelineModel, MonitorProxy *proxy)
0110 {
0111     QMutexLocker lk(&m_lock);
0112     if (count() == 1 && m_activeTimeline) {
0113         m_activeTimeline->model()->updateVisibleSequenceName(QString());
0114     }
0115     disconnect(this, &TimelineTabs::currentChanged, this, &TimelineTabs::connectCurrent);
0116     TimelineWidget *newTimeline = new TimelineWidget(uuid, this);
0117     newTimeline->setTimelineMenu(m_timelineClipMenu, m_timelineCompositionMenu, m_timelineMenu, m_guideMenu, m_timelineRulerMenu, m_editGuideAction,
0118                                  m_headerMenu, m_thumbsMenu, m_timelineSubtitleClipMenu);
0119     newTimeline->setModel(timelineModel, proxy);
0120     int newIndex = addTab(newTimeline, tabName);
0121     setCurrentIndex(newIndex);
0122     connectCurrent(newIndex);
0123     setTabsClosable(count() > 1);
0124     if (count() == 2) {
0125         updateWindowTitle();
0126     }
0127     connect(this, &TimelineTabs::currentChanged, this, &TimelineTabs::connectCurrent);
0128     return newTimeline;
0129 }
0130 
0131 void TimelineTabs::connectCurrent(int ix)
0132 {
0133     QUuid previousTab = QUuid();
0134     if (m_activeTimeline && m_activeTimeline->model()) {
0135         previousTab = m_activeTimeline->getUuid();
0136         qDebug() << "===== DISCONNECTING PREVIOUS: " << previousTab;
0137         pCore->window()->disableMulticam();
0138         int pos = pCore->getMonitorPosition();
0139         m_activeTimeline->model()->updateDuration();
0140         int duration = m_activeTimeline->model()->duration();
0141         m_activeTimeline->controller()->saveSequenceProperties();
0142         pCore->bin()->updateSequenceClip(previousTab, duration, pos);
0143         pCore->window()->disconnectTimeline(m_activeTimeline);
0144         disconnectTimeline(m_activeTimeline);
0145     } else {
0146         qDebug() << "==== NO PREVIOUS TIMELINE";
0147     }
0148     if (ix < 0 || ix >= count() || pCore->currentDoc()->closing) {
0149         m_activeTimeline = nullptr;
0150         qDebug() << "==== ABORTING NO TIMELINE AVAILABLE";
0151         return;
0152     }
0153     m_activeTimeline = static_cast<TimelineWidget *>(widget(ix));
0154     if (m_activeTimeline->model() == nullptr || m_activeTimeline->model()->m_closing) {
0155         // Closing app
0156         qDebug() << "++++++++++++\n\nCLOSING APP\n\n+++++++++++++";
0157         return;
0158     }
0159     pCore->window()->connectTimeline();
0160     connectTimeline(m_activeTimeline);
0161     updateWindowTitle();
0162     if (!m_activeTimeline->model()->isLoading) {
0163         pCore->bin()->sequenceActivated();
0164     }
0165 }
0166 
0167 void TimelineTabs::renameTab(const QUuid &uuid, const QString &name)
0168 {
0169     qDebug() << "==== READY TO RENAME!!!!!!!!!";
0170     for (int i = 0; i < count(); i++) {
0171         if (static_cast<TimelineWidget *>(widget(i))->getUuid() == uuid) {
0172             tabBar()->setTabText(i, name);
0173             pCore->projectManager()->setTimelinePropery(uuid, QStringLiteral("kdenlive:clipname"), name);
0174             updateWindowTitle();
0175             break;
0176         }
0177     }
0178 }
0179 
0180 void TimelineTabs::closeTimelineByIndex(int ix)
0181 {
0182     TimelineWidget *timeline = static_cast<TimelineWidget *>(widget(ix));
0183     if (timeline == m_activeTimeline) {
0184         Q_EMIT timeline->model()->requestClearAssetView(-1);
0185         pCore->clearTimeRemap();
0186         pCore->mixer()->unsetModel();
0187         pCore->window()->disableMulticam();
0188         m_activeTimeline->model()->updateDuration();
0189         timeline->controller()->saveSequenceProperties();
0190     }
0191     const QString seqName = tabText(ix);
0192     std::shared_ptr<TimelineItemModel> model = timeline->model();
0193     const QUuid uuid = timeline->getUuid();
0194     const QString id = pCore->projectItemModel()->getSequenceId(uuid);
0195     Fun undo = [uuid, id, model]() {
0196         model->registerTimeline();
0197         return pCore->projectManager()->openTimeline(id, uuid, -1, false, model);
0198     };
0199     Fun redo = [this, ix, uuid]() {
0200         pCore->projectManager()->closeTimeline(uuid, false, false);
0201         TimelineWidget *timeline = static_cast<TimelineWidget *>(widget(ix));
0202         timeline->blockSignals(true);
0203         timeline->setSource(QUrl());
0204         if (timeline == m_activeTimeline) {
0205             pCore->window()->disconnectTimeline(timeline);
0206             disconnectTimeline(timeline);
0207         }
0208         timeline->unsetModel();
0209         if (m_activeTimeline == timeline) {
0210             m_activeTimeline = nullptr;
0211         }
0212         delete timeline;
0213         updateWindowTitle();
0214         return true;
0215     };
0216     redo();
0217     pCore->pushUndo(undo, redo, i18n("Close %1", seqName));
0218 }
0219 
0220 TimelineWidget *TimelineTabs::getCurrentTimeline() const
0221 {
0222     return m_activeTimeline;
0223 }
0224 
0225 void TimelineTabs::closeTimelines()
0226 {
0227     for (int i = 0; i < count(); i++) {
0228         static_cast<TimelineWidget *>(widget(i))->unsetModel();
0229     }
0230 }
0231 
0232 void TimelineTabs::closeTimelineTab(const QUuid uuid)
0233 {
0234     QMutexLocker lk(&m_lock);
0235     int currentCount = count();
0236     for (int i = 0; i < currentCount; i++) {
0237         TimelineWidget *timeline = static_cast<TimelineWidget *>(widget(i));
0238         if (uuid == timeline->getUuid()) {
0239             timeline->blockSignals(true);
0240             timeline->setSource(QUrl());
0241             timeline->blockSignals(true);
0242             if (timeline == m_activeTimeline) {
0243                 Q_EMIT showSubtitle(-1);
0244                 pCore->window()->disconnectTimeline(timeline);
0245                 disconnectTimeline(timeline);
0246             }
0247             timeline->unsetModel();
0248             if (m_activeTimeline == timeline) {
0249                 m_activeTimeline = nullptr;
0250             }
0251             delete timeline;
0252             // pCore->projectManager()->closeTimeline(uuid);
0253             setTabsClosable(count() > 1);
0254             if (currentCount == 2) {
0255                 updateWindowTitle();
0256             }
0257             break;
0258         }
0259     }
0260 }
0261 
0262 void TimelineTabs::connectTimeline(TimelineWidget *timeline)
0263 {
0264     connect(timeline, &TimelineWidget::focusProjectMonitor, pCore->monitorManager(), &MonitorManager::focusProjectMonitor, Qt::DirectConnection);
0265     connect(this, &TimelineTabs::audioThumbFormatChanged, timeline->controller(), &TimelineController::audioThumbFormatChanged);
0266     connect(this, &TimelineTabs::showThumbnailsChanged, timeline->controller(), &TimelineController::showThumbnailsChanged);
0267     connect(this, &TimelineTabs::showAudioThumbnailsChanged, timeline->controller(), &TimelineController::showAudioThumbnailsChanged);
0268     connect(this, &TimelineTabs::changeZoom, timeline, &TimelineWidget::slotChangeZoom);
0269     connect(this, &TimelineTabs::fitZoom, timeline, &TimelineWidget::slotFitZoom);
0270     connect(timeline->controller(), &TimelineController::showTransitionModel, this, &TimelineTabs::showTransitionModel);
0271     connect(timeline->controller(), &TimelineController::showMixModel, this, &TimelineTabs::showMixModel);
0272     connect(timeline->controller(), &TimelineController::updateZoom, this, [&](double value) { Q_EMIT updateZoom(getCurrentTimeline()->zoomForScale(value)); });
0273     connect(timeline->controller(), &TimelineController::showItemEffectStack, this, &TimelineTabs::showItemEffectStack);
0274     connect(timeline->controller(), &TimelineController::showSubtitle, this, &TimelineTabs::showSubtitle);
0275     connect(timeline->controller(), &TimelineController::updateAssetPosition, this, &TimelineTabs::updateAssetPosition);
0276     connect(timeline->controller(), &TimelineController::centerView, timeline, &TimelineWidget::slotCenterView);
0277 
0278     connect(pCore->monitorManager()->projectMonitor(), &Monitor::zoneUpdated, m_activeTimeline, &TimelineWidget::zoneUpdated);
0279     connect(pCore->monitorManager()->projectMonitor(), &Monitor::zoneUpdatedWithUndo, m_activeTimeline, &TimelineWidget::zoneUpdatedWithUndo);
0280     connect(m_activeTimeline, &TimelineWidget::zoneMoved, pCore->monitorManager()->projectMonitor(), &Monitor::slotLoadClipZone);
0281     connect(pCore->monitorManager()->projectMonitor(), &Monitor::addTimelineEffect, m_activeTimeline->controller(),
0282             &TimelineController::addEffectToCurrentClip);
0283     timeline->rootContext()->setContextProperty("proxy", pCore->monitorManager()->projectMonitor()->getControllerProxy());
0284     Q_EMIT timeline->controller()->selectionChanged();
0285 }
0286 
0287 void TimelineTabs::disconnectTimeline(TimelineWidget *timeline)
0288 {
0289     timeline->rootContext()->setContextProperty("proxy", nullptr);
0290     disconnect(timeline, &TimelineWidget::focusProjectMonitor, pCore->monitorManager(), &MonitorManager::focusProjectMonitor);
0291     disconnect(this, &TimelineTabs::audioThumbFormatChanged, timeline->controller(), &TimelineController::audioThumbFormatChanged);
0292     disconnect(this, &TimelineTabs::showThumbnailsChanged, timeline->controller(), &TimelineController::showThumbnailsChanged);
0293     disconnect(this, &TimelineTabs::showAudioThumbnailsChanged, timeline->controller(), &TimelineController::showAudioThumbnailsChanged);
0294     disconnect(this, &TimelineTabs::changeZoom, timeline, &TimelineWidget::slotChangeZoom);
0295     disconnect(this, &TimelineTabs::fitZoom, timeline, &TimelineWidget::slotFitZoom);
0296     disconnect(timeline->controller(), &TimelineController::showTransitionModel, this, &TimelineTabs::showTransitionModel);
0297     disconnect(timeline->controller(), &TimelineController::showMixModel, this, &TimelineTabs::showMixModel);
0298     disconnect(timeline->controller(), &TimelineController::showItemEffectStack, this, &TimelineTabs::showItemEffectStack);
0299     disconnect(timeline->controller(), &TimelineController::showSubtitle, this, &TimelineTabs::showSubtitle);
0300     disconnect(timeline->controller(), &TimelineController::updateAssetPosition, this, &TimelineTabs::updateAssetPosition);
0301 
0302     disconnect(pCore->monitorManager()->projectMonitor(), &Monitor::zoneUpdated, timeline, &TimelineWidget::zoneUpdated);
0303     disconnect(pCore->monitorManager()->projectMonitor(), &Monitor::zoneUpdatedWithUndo, timeline, &TimelineWidget::zoneUpdatedWithUndo);
0304     disconnect(timeline, &TimelineWidget::zoneMoved, pCore->monitorManager()->projectMonitor(), &Monitor::slotLoadClipZone);
0305     disconnect(pCore->monitorManager()->projectMonitor(), &Monitor::addTimelineEffect, timeline->controller(), &TimelineController::addEffectToCurrentClip);
0306 }
0307 
0308 void TimelineTabs::buildClipMenu()
0309 {
0310     // Timeline clip menu
0311     delete m_timelineClipMenu;
0312     m_timelineClipMenu = new QMenu(this);
0313     KActionCollection *coll = pCore->window()->actionCollection();
0314     m_timelineClipMenu->addAction(coll->action(QStringLiteral("edit_copy")));
0315     m_timelineClipMenu->addAction(coll->action(QStringLiteral("paste_effects")));
0316     m_timelineClipMenu->addAction(coll->action(QStringLiteral("delete_effects")));
0317     m_timelineClipMenu->addAction(coll->action(QStringLiteral("group_clip")));
0318     m_timelineClipMenu->addAction(coll->action(QStringLiteral("ungroup_clip")));
0319     m_timelineClipMenu->addAction(coll->action(QStringLiteral("edit_item_duration")));
0320     m_timelineClipMenu->addAction(coll->action(QStringLiteral("clip_split")));
0321     m_timelineClipMenu->addAction(coll->action(QStringLiteral("clip_switch")));
0322     m_timelineClipMenu->addAction(coll->action(QStringLiteral("delete_timeline_clip")));
0323     m_timelineClipMenu->addAction(coll->action(QStringLiteral("extract_clip")));
0324     m_timelineClipMenu->addAction(coll->action(QStringLiteral("save_to_bin")));
0325     m_timelineClipMenu->addAction(coll->action(QStringLiteral("send_sequence")));
0326 
0327     QMenu *markerMenu = static_cast<QMenu *>(pCore->window()->factory()->container(QStringLiteral("marker_menu"), pCore->window()));
0328     m_timelineClipMenu->addMenu(markerMenu);
0329 
0330     m_timelineClipMenu->addAction(coll->action(QStringLiteral("set_audio_align_ref")));
0331     m_timelineClipMenu->addAction(coll->action(QStringLiteral("align_audio")));
0332     m_timelineClipMenu->addAction(coll->action(QStringLiteral("edit_item_speed")));
0333     m_timelineClipMenu->addAction(coll->action(QStringLiteral("edit_item_remap")));
0334     m_timelineClipMenu->addAction(coll->action(QStringLiteral("clip_in_project_tree")));
0335     m_timelineClipMenu->addAction(coll->action(QStringLiteral("cut_timeline_clip")));
0336 }
0337 
0338 void TimelineTabs::setTimelineMenu(QMenu *compositionMenu, QMenu *timelineMenu, QMenu *guideMenu, QMenu *timelineRulerMenu, QAction *editGuideAction,
0339                                    QMenu *headerMenu, QMenu *thumbsMenu, QMenu *subtitleClipMenu)
0340 {
0341     buildClipMenu();
0342     m_timelineCompositionMenu = compositionMenu;
0343     m_timelineMenu = timelineMenu;
0344     m_timelineRulerMenu = timelineRulerMenu;
0345     m_guideMenu = guideMenu;
0346     m_headerMenu = headerMenu;
0347     m_thumbsMenu = thumbsMenu;
0348     m_headerMenu->addMenu(m_thumbsMenu);
0349     m_timelineSubtitleClipMenu = subtitleClipMenu;
0350     m_editGuideAction = editGuideAction;
0351 }
0352 
0353 const QStringList TimelineTabs::openedSequences()
0354 {
0355     QStringList result;
0356     for (int i = 0; i < count(); i++) {
0357         result << static_cast<TimelineWidget *>(widget(i))->getUuid().toString();
0358     }
0359     return result;
0360 }
0361 
0362 TimelineWidget *TimelineTabs::getTimeline(const QUuid uuid) const
0363 {
0364     for (int i = 0; i < count(); i++) {
0365         TimelineWidget *tl = static_cast<TimelineWidget *>(widget(i));
0366         if (tl->getUuid() == uuid) {
0367             return tl;
0368         }
0369     }
0370     return nullptr;
0371 }
0372 
0373 void TimelineTabs::slotNextSequence()
0374 {
0375     int max = count();
0376     int focus = currentIndex() + 1;
0377     if (focus >= max) {
0378         focus = 0;
0379     }
0380     setCurrentIndex(focus);
0381 }
0382 
0383 void TimelineTabs::slotPreviousSequence()
0384 {
0385     int focus = currentIndex() - 1;
0386     if (focus < 0) {
0387         focus = count() - 1;
0388     }
0389     setCurrentIndex(focus);
0390 }
0391 
0392 void TimelineTabs::onTabBarDoubleClicked(int index)
0393 {
0394     const QString currentTabName = KLocalizedString::removeAcceleratorMarker(tabBar()->tabText(index));
0395     bool ok = false;
0396     const QString newName = QInputDialog::getText(this, i18n("Rename Sequence"), i18n("Rename Sequence"), QLineEdit::Normal, currentTabName, &ok);
0397     if (ok && !newName.isEmpty()) {
0398         TimelineWidget *timeline = static_cast<TimelineWidget *>(widget(index));
0399         if (timeline) {
0400             const QString id = pCore->projectItemModel()->getSequenceId(timeline->getUuid());
0401             std::shared_ptr<ProjectClip> clip = pCore->projectItemModel()->getClipByBinID(id);
0402             if (clip) {
0403                 clip->rename(newName, 0);
0404             }
0405         }
0406     }
0407 }