File indexing completed on 2024-05-12 08:54:14
0001 /* 0002 SPDX-FileCopyrightText: 2007 Jean-Baptiste Mardelle <jb@kdenlive.org> 0003 0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "monitor.h" 0008 #include "bin/bin.h" 0009 #include "bin/projectclip.h" 0010 #include "capture/mediacapture.h" 0011 #include "core.h" 0012 #include "dialogs/profilesdialog.h" 0013 #include "doc/kdenlivedoc.h" 0014 #include "doc/kthumb.h" 0015 #include "jobs/cuttask.h" 0016 #include "kdenlivesettings.h" 0017 #include "lib/audio/audioStreamInfo.h" 0018 #include "lib/localeHandling.h" 0019 #include "mainwindow.h" 0020 #include "mltcontroller/clipcontroller.h" 0021 #include "project/dialogs/guideslist.h" 0022 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0023 #include "videowidget.h" 0024 #if defined(Q_OS_WIN) 0025 #include "d3dvideowidget.h" 0026 #include "openglvideowidget.h" 0027 #elif defined(Q_OS_MACOS) 0028 #include "metalvideowidget.h" 0029 #else 0030 // Linux 0031 #include "openglvideowidget.h" 0032 #endif 0033 #else // Qt6 0034 #include "glwidget.h" 0035 #endif 0036 0037 #include "bin/model/markersortmodel.h" 0038 #include "monitormanager.h" 0039 #include "monitorproxy.h" 0040 #include "profiles/profilemodel.hpp" 0041 #include "project/projectmanager.h" 0042 #include "qmlmanager.h" 0043 #include "recmanager.h" 0044 #include "scopes/monitoraudiolevel.h" 0045 #include "timeline2/model/snapmodel.hpp" 0046 #include "timeline2/view/timelinecontroller.h" 0047 #include "timeline2/view/timelinewidget.h" 0048 #include "transitions/transitionsrepository.hpp" 0049 #include "utils/thumbnailcache.hpp" 0050 0051 #include "KLocalizedString" 0052 #include <KActionMenu> 0053 #include <KDualAction> 0054 #include <KFileWidget> 0055 #include <KMessageBox> 0056 #include <KMessageWidget> 0057 #include <KRecentDirs> 0058 #include <KSelectAction> 0059 #include <KWindowConfig> 0060 #include <kio_version.h> 0061 #include <kwidgetsaddons_version.h> 0062 0063 #include "kdenlive_debug.h" 0064 #include <QCheckBox> 0065 #include <QDrag> 0066 #include <QFontDatabase> 0067 #include <QMenu> 0068 #include <QMimeData> 0069 #include <QMouseEvent> 0070 #include <QQuickItem> 0071 #include <QScreen> 0072 #include <QScrollBar> 0073 #include <QSlider> 0074 #include <QToolButton> 0075 #include <QVBoxLayout> 0076 #include <QtConcurrent> 0077 #include <utility> 0078 0079 #define SEEK_INACTIVE (-1) 0080 0081 VolumeAction::VolumeAction(QObject *parent) 0082 : QWidgetAction(parent) 0083 { 0084 } 0085 0086 QWidget *VolumeAction::createWidget(QWidget *parent) 0087 { 0088 auto *hlay = new QHBoxLayout(parent); 0089 auto *iconLabel = new QLabel(); 0090 iconLabel->setToolTip(i18n("Audio volume")); 0091 auto *slider = new QSlider(Qt::Horizontal, parent); 0092 slider->setRange(0, 100); 0093 auto *percentLabel = new QLabel(parent); 0094 connect(slider, &QSlider::valueChanged, this, [percentLabel, iconLabel](int value) { 0095 percentLabel->setText(i18n("%1%", value)); 0096 int h = 16; 0097 QString iconName(QStringLiteral("audio-volume-high")); 0098 if (value == 0) { 0099 iconName = QStringLiteral("audio-volume-muted"); 0100 } else if (value < 33) { 0101 iconName = QStringLiteral("audio-volume-low"); 0102 } else if (value < 66) { 0103 iconName = QStringLiteral("audio-volume-medium"); 0104 } 0105 iconLabel->setPixmap(QIcon::fromTheme(iconName).pixmap(h, h)); 0106 }); 0107 slider->setValue(KdenliveSettings::volume()); 0108 connect(slider, &QSlider::valueChanged, this, &VolumeAction::volumeChanged); 0109 hlay->addWidget(iconLabel); 0110 hlay->addWidget(slider); 0111 hlay->addWidget(percentLabel); 0112 auto w = new QWidget(parent); 0113 w->setLayout(hlay); 0114 return w; 0115 } 0116 0117 Monitor::Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *parent) 0118 : AbstractMonitor(id, manager, parent) 0119 , m_controller(nullptr) 0120 , m_glMonitor(nullptr) 0121 , m_snaps(new SnapModel()) 0122 , m_splitEffect(nullptr) 0123 , m_splitProducer(nullptr) 0124 , m_dragStarted(false) 0125 , m_recManager(nullptr) 0126 , m_loopClipAction(nullptr) 0127 , m_contextMenu(nullptr) 0128 , m_markerMenu(nullptr) 0129 , m_audioChannels(nullptr) 0130 , m_loopClipTransition(true) 0131 , m_editMarker(nullptr) 0132 , m_forceSizeFactor(0) 0133 , m_lastMonitorSceneType(MonitorSceneDefault) 0134 , m_displayingCountdown(true) 0135 { 0136 auto *layout = new QVBoxLayout; 0137 layout->setContentsMargins(0, 0, 0, 0); 0138 layout->setSpacing(0); 0139 // Create container widget 0140 m_glWidget = new QWidget(this); 0141 m_glWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); 0142 auto *glayout = new QGridLayout(m_glWidget); 0143 glayout->setSpacing(0); 0144 glayout->setContentsMargins(0, 0, 0, 0); 0145 // Create QML OpenGL widget 0146 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0147 #if defined(Q_OS_WIN) 0148 if (QSGRendererInterface::Direct3D11 == QQuickWindow::graphicsApi()) 0149 m_glMonitor = new D3DVideoWidget(id, this); 0150 else 0151 m_glMonitor = new OpenGLVideoWidget(id, this); 0152 #elif defined(Q_OS_MACOS) 0153 m_glMonitor = new MetalVideoWidget(id, this); 0154 #else 0155 m_glMonitor = new OpenGLVideoWidget(id, this); 0156 #endif 0157 // The m_glMonitor quickWindow() can be destroyed on undock with some graphics interface (Windows/Mac), so reconnect on destroy 0158 auto rebuildViewConnection = [this]() { 0159 connect(m_glMonitor->quickWindow(), &QQuickWindow::sceneGraphInitialized, m_glMonitor, &VideoWidget::initialize, Qt::DirectConnection); 0160 connect(m_glMonitor->quickWindow(), &QQuickWindow::beforeRendering, m_glMonitor, &VideoWidget::beforeRendering, Qt::DirectConnection); 0161 connect(m_glMonitor->quickWindow(), &QQuickWindow::beforeRenderPassRecording, m_glMonitor, &VideoWidget::renderVideo, Qt::DirectConnection); 0162 m_glMonitor->reconnectWindow(); 0163 }; 0164 0165 connect(m_glMonitor, &VideoWidget::reconnectWindow, [this, rebuildViewConnection]() { 0166 connect(m_glMonitor->quickWindow(), &QQuickWindow::destroyed, [rebuildViewConnection]() { rebuildViewConnection(); }); 0167 }); 0168 0169 rebuildViewConnection(); 0170 #else 0171 m_glMonitor = new VideoWidget(id, this); 0172 #endif 0173 connect(m_glMonitor, &VideoWidget::passKeyEvent, this, &Monitor::doKeyPressEvent); 0174 connect(m_glMonitor, &VideoWidget::panView, this, &Monitor::panView); 0175 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::requestSeek, this, &Monitor::processSeek, Qt::DirectConnection); 0176 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::positionChanged, this, &Monitor::slotSeekPosition); 0177 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::addTimelineEffect, this, &Monitor::addTimelineEffect); 0178 0179 m_qmlManager = new QmlManager(m_glMonitor); 0180 connect(m_qmlManager, &QmlManager::effectChanged, this, &Monitor::effectChanged); 0181 connect(m_qmlManager, &QmlManager::effectPointsChanged, this, &Monitor::effectPointsChanged); 0182 connect(m_qmlManager, &QmlManager::activateTrack, this, [&](int ix) { Q_EMIT activateTrack(ix, false); }); 0183 0184 glayout->addWidget(m_glMonitor, 0, 0); 0185 m_verticalScroll = new QScrollBar(Qt::Vertical); 0186 glayout->addWidget(m_verticalScroll, 0, 1); 0187 m_verticalScroll->hide(); 0188 m_horizontalScroll = new QScrollBar(Qt::Horizontal); 0189 glayout->addWidget(m_horizontalScroll, 1, 0); 0190 m_horizontalScroll->hide(); 0191 connect(m_horizontalScroll, &QAbstractSlider::valueChanged, this, &Monitor::setOffsetX); 0192 connect(m_verticalScroll, &QAbstractSlider::valueChanged, this, &Monitor::setOffsetY); 0193 connect(m_glMonitor, &VideoWidget::frameDisplayed, this, &Monitor::onFrameDisplayed, Qt::DirectConnection); 0194 connect(m_glMonitor, &VideoWidget::mouseSeek, this, &Monitor::slotMouseSeek); 0195 connect(m_glMonitor, &VideoWidget::switchFullScreen, this, &Monitor::slotSwitchFullScreen); 0196 connect(m_glMonitor, &VideoWidget::zoomChanged, this, &Monitor::setZoom); 0197 connect(m_glMonitor, SIGNAL(lockMonitor(bool)), this, SLOT(slotLockMonitor(bool)), Qt::DirectConnection); 0198 connect(m_glMonitor, &VideoWidget::showContextMenu, this, &Monitor::slotShowMenu); 0199 connect(m_glMonitor, &VideoWidget::gpuNotSupported, this, &Monitor::gpuError); 0200 0201 m_glWidget->setMinimumSize(QSize(320, 180)); 0202 layout->addWidget(m_glWidget, 10); 0203 layout->addStretch(); 0204 0205 // Tool bar buttons 0206 m_toolbar = new QToolBar(this); 0207 int size = style()->pixelMetric(QStyle::PM_SmallIconSize); 0208 QSize iconSize(size, size); 0209 m_toolbar->setIconSize(iconSize); 0210 0211 auto *scalingAction = new QComboBox(this); 0212 scalingAction->setToolTip(i18n("Preview resolution - lower resolution means faster preview")); 0213 scalingAction->setWhatsThis(xi18nc("@info:whatsthis", "Sets the preview resolution of the project/clip monitor. One can select between 1:1, 720p, 540p, " 0214 "360p, 270p (the lower the resolution the faster the preview).")); 0215 // Combobox padding is bad, so manually add a space before text 0216 scalingAction->addItems({QStringLiteral(" ") + i18n("1:1"), QStringLiteral(" ") + i18n("720p"), QStringLiteral(" ") + i18n("540p"), 0217 QStringLiteral(" ") + i18n("360p"), QStringLiteral(" ") + i18n("270p")}); 0218 connect(scalingAction, QOverload<int>::of(&QComboBox::activated), this, [this](int index) { 0219 switch (index) { 0220 case 1: 0221 KdenliveSettings::setPreviewScaling(2); 0222 break; 0223 case 2: 0224 KdenliveSettings::setPreviewScaling(4); 0225 break; 0226 case 3: 0227 KdenliveSettings::setPreviewScaling(8); 0228 break; 0229 case 4: 0230 KdenliveSettings::setPreviewScaling(16); 0231 break; 0232 default: 0233 KdenliveSettings::setPreviewScaling(0); 0234 } 0235 Q_EMIT m_monitorManager->scalingChanged(); 0236 Q_EMIT m_monitorManager->updatePreviewScaling(); 0237 m_monitorManager->refreshMonitors(); 0238 }); 0239 0240 connect(manager, &MonitorManager::updatePreviewScaling, this, [this, scalingAction]() { 0241 m_glMonitor->updateScaling(); 0242 switch (KdenliveSettings::previewScaling()) { 0243 case 2: 0244 scalingAction->setCurrentIndex(1); 0245 break; 0246 case 4: 0247 scalingAction->setCurrentIndex(2); 0248 break; 0249 case 8: 0250 scalingAction->setCurrentIndex(3); 0251 break; 0252 case 16: 0253 scalingAction->setCurrentIndex(4); 0254 break; 0255 default: 0256 scalingAction->setCurrentIndex(0); 0257 break; 0258 } 0259 }); 0260 scalingAction->setFrame(false); 0261 scalingAction->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 0262 m_toolbar->addWidget(scalingAction); 0263 m_toolbar->addSeparator(); 0264 0265 if (id == Kdenlive::ClipMonitor) { 0266 // Add options for recording 0267 m_recManager = new RecManager(this); 0268 connect(m_recManager, &RecManager::warningMessage, this, &Monitor::warningMessage); 0269 connect(m_recManager, &RecManager::addClipToProject, this, &Monitor::addClipToProject); 0270 connect(m_glMonitor, &VideoWidget::startDrag, this, &Monitor::slotStartDrag); 0271 connect(pCore.get(), &Core::binClipDeleted, m_glMonitor->getControllerProxy(), &MonitorProxy::clipDeleted); 0272 // Show timeline clip usage 0273 connect(pCore.get(), &Core::clipInstanceResized, this, [this](const QString &binId) { 0274 if (m_controller && activeClipId() == binId) { 0275 m_controller->checkClipBounds(); 0276 } 0277 }); 0278 0279 m_toolbar->addAction(manager->getAction(QStringLiteral("insert_project_tree"))); 0280 m_toolbar->addSeparator(); 0281 m_streamsButton = new QToolButton(this); 0282 m_streamsButton->setPopupMode(QToolButton::InstantPopup); 0283 m_streamsButton->setIcon(QIcon::fromTheme(QStringLiteral("speaker"))); 0284 m_streamsButton->setToolTip(i18n("Audio streams")); 0285 m_streamAction = m_toolbar->addWidget(m_streamsButton); 0286 m_audioChannels = new QMenu(this); 0287 m_streamsButton->setMenu(m_audioChannels); 0288 m_streamAction->setVisible(false); 0289 0290 // Connect job data 0291 connect(&pCore->taskManager, &TaskManager::detailedProgress, m_glMonitor->getControllerProxy(), &MonitorProxy::setJobsProgress); 0292 0293 connect(m_audioChannels, &QMenu::triggered, this, [this](QAction *ac) { 0294 // m_audioChannels->show(); 0295 QList<QAction *> actions = m_audioChannels->actions(); 0296 QMap<int, QString> enabledStreams; 0297 QVector<int> streamsList; 0298 if (ac->data().toInt() == INT_MAX) { 0299 // Merge stream selected, clear all others 0300 enabledStreams.clear(); 0301 enabledStreams.insert(INT_MAX, i18n("Merged streams")); 0302 // Disable all other streams 0303 QSignalBlocker bk(m_audioChannels); 0304 for (auto act : qAsConst(actions)) { 0305 if (act->isChecked() && act != ac) { 0306 act->setChecked(false); 0307 } 0308 if (act->data().toInt() != INT_MAX) { 0309 streamsList << act->data().toInt(); 0310 } 0311 } 0312 } else { 0313 for (auto act : qAsConst(actions)) { 0314 if (act->isChecked()) { 0315 // Audio stream is selected 0316 if (act->data().toInt() == INT_MAX) { 0317 QSignalBlocker bk(m_audioChannels); 0318 act->setChecked(false); 0319 } else { 0320 enabledStreams.insert(act->data().toInt(), act->text().remove(QLatin1Char('&'))); 0321 } 0322 } 0323 if (act->data().toInt() != INT_MAX) { 0324 streamsList << act->data().toInt(); 0325 } 0326 } 0327 } 0328 if (!enabledStreams.isEmpty()) { 0329 // Only 1 stream wanted, easy 0330 QMap<QString, QString> props; 0331 props.insert(QStringLiteral("audio_index"), QString::number(enabledStreams.firstKey())); 0332 props.insert(QStringLiteral("astream"), QString::number(streamsList.indexOf(enabledStreams.firstKey()))); 0333 QList<int> streams = enabledStreams.keys(); 0334 QStringList astreams; 0335 for (const int st : qAsConst(streams)) { 0336 astreams << QString::number(st); 0337 } 0338 props.insert(QStringLiteral("kdenlive:active_streams"), astreams.join(QLatin1Char(';'))); 0339 m_controller->setProperties(props, true); 0340 } else { 0341 // No active stream 0342 QMap<QString, QString> props; 0343 props.insert(QStringLiteral("audio_index"), QStringLiteral("-1")); 0344 props.insert(QStringLiteral("astream"), QStringLiteral("-1")); 0345 props.insert(QStringLiteral("kdenlive:active_streams"), QStringLiteral("-1")); 0346 m_controller->setProperties(props, true); 0347 } 0348 }); 0349 } else if (id == Kdenlive::ProjectMonitor) { 0350 // JBM - This caused the track audio levels to go blank on pause, doesn't seem to have another use 0351 // connect(m_glMonitor, &VideoWidget::paused, m_monitorManager, &MonitorManager::cleanMixer); 0352 } 0353 0354 m_markIn = new QAction(QIcon::fromTheme(QStringLiteral("zone-in")), i18n("Set Zone In"), this); 0355 m_markOut = new QAction(QIcon::fromTheme(QStringLiteral("zone-out")), i18n("Set Zone Out"), this); 0356 m_toolbar->addAction(m_markIn); 0357 m_toolbar->addAction(m_markOut); 0358 connect(m_markIn, &QAction::triggered, this, [&, manager]() { 0359 m_monitorManager->activateMonitor(m_id); 0360 manager->getAction(QStringLiteral("mark_in"))->trigger(); 0361 }); 0362 connect(m_markOut, &QAction::triggered, this, [&, manager]() { 0363 m_monitorManager->activateMonitor(m_id); 0364 manager->getAction(QStringLiteral("mark_out"))->trigger(); 0365 }); 0366 // Per monitor rewind action 0367 QAction *rewind = new QAction(QIcon::fromTheme(QStringLiteral("media-seek-backward")), i18n("Rewind"), this); 0368 m_toolbar->addAction(rewind); 0369 connect(rewind, &QAction::triggered, this, [&]() { Monitor::slotRewind(); }); 0370 0371 auto *playButton = new QToolButton(m_toolbar); 0372 m_playMenu = new QMenu(i18n("Play"), this); 0373 connect(m_playMenu, &QMenu::aboutToShow, this, &Monitor::slotActivateMonitor); 0374 QAction *originalPlayAction = static_cast<KDualAction *>(manager->getAction(QStringLiteral("monitor_play"))); 0375 m_playAction = new KDualAction(i18n("Play"), i18n("Pause"), this); 0376 m_playAction->setInactiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); 0377 m_playAction->setActiveIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause"))); 0378 connect(m_glMonitor, &VideoWidget::monitorPlay, m_playAction, &QAction::trigger); 0379 0380 QString strippedTooltip = m_playAction->toolTip().remove(QRegularExpression(QStringLiteral("\\s\\(.*\\)"))); 0381 // append shortcut if it exists for action 0382 if (originalPlayAction->shortcut() == QKeySequence(0)) { 0383 m_playAction->setToolTip(strippedTooltip); 0384 } else { 0385 m_playAction->setToolTip(strippedTooltip + QStringLiteral(" (") + originalPlayAction->shortcut().toString() + QLatin1Char(')')); 0386 } 0387 m_playMenu->addAction(m_playAction); 0388 connect(m_playAction, &QAction::triggered, this, &Monitor::slotSwitchPlay); 0389 0390 playButton->setMenu(m_playMenu); 0391 playButton->setPopupMode(QToolButton::MenuButtonPopup); 0392 m_toolbar->addWidget(playButton); 0393 0394 // Per monitor forward action 0395 QAction *forward = new QAction(QIcon::fromTheme(QStringLiteral("media-seek-forward")), i18n("Forward"), this); 0396 m_toolbar->addAction(forward); 0397 connect(forward, &QAction::triggered, this, [this]() { Monitor::slotForward(); }); 0398 0399 m_configMenuAction = new KActionMenu(QIcon::fromTheme(QStringLiteral("application-menu")), i18n("More Options…"), m_toolbar); 0400 m_configMenuAction->setWhatsThis(xi18nc("@info:whatsthis", "Opens the list of project/clip monitor options (e.g. audio volume, monitor size).")); 0401 m_configMenuAction->setPopupMode(QToolButton::InstantPopup); 0402 connect(m_configMenuAction->menu(), &QMenu::aboutToShow, this, &Monitor::updateMarkers); 0403 0404 playButton->setDefaultAction(m_playAction); 0405 auto *volumeAction = new VolumeAction(this); 0406 connect(volumeAction, &VolumeAction::volumeChanged, this, &Monitor::slotSetVolume); 0407 m_configMenuAction->addAction(volumeAction); 0408 0409 m_markerMenu = new KActionMenu(id == Kdenlive::ClipMonitor ? i18n("Go to Marker…") : i18n("Go to Guide…"), this); 0410 m_markerMenu->setEnabled(false); 0411 m_configMenuAction->addAction(m_markerMenu); 0412 connect(m_markerMenu->menu(), &QMenu::triggered, this, &Monitor::slotGoToMarker); 0413 m_forceSize = new KSelectAction(QIcon::fromTheme(QStringLiteral("transform-scale")), i18n("Force Monitor Size"), this); 0414 QAction *fullAction = m_forceSize->addAction(QIcon(), i18n("Force 100%")); 0415 fullAction->setData(100); 0416 QAction *halfAction = m_forceSize->addAction(QIcon(), i18n("Force 50%")); 0417 halfAction->setData(50); 0418 QAction *freeAction = m_forceSize->addAction(QIcon(), i18n("Free Resize")); 0419 freeAction->setData(0); 0420 m_configMenuAction->addAction(m_forceSize); 0421 m_forceSize->setCurrentAction(freeAction); 0422 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 240, 0) 0423 connect(m_forceSize, &KSelectAction::actionTriggered, this, &Monitor::slotForceSize); 0424 #else 0425 connect(m_forceSize, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, &Monitor::slotForceSize); 0426 #endif 0427 0428 if (m_id == Kdenlive::ClipMonitor) { 0429 m_background = new KSelectAction(QIcon::fromTheme(QStringLiteral("paper-color")), i18n("Background Color"), this); 0430 QAction *blackAction = m_background->addAction(QIcon(), i18n("Black")); 0431 blackAction->setData("black"); 0432 QAction *whiteAction = m_background->addAction(QIcon(), i18n("White")); 0433 whiteAction->setData("white"); 0434 QAction *pinkAction = m_background->addAction(QIcon(), i18n("Pink")); 0435 pinkAction->setData("#ff00ff"); 0436 m_configMenuAction->addAction(m_background); 0437 if (KdenliveSettings::monitor_background() == whiteAction->data().toString()) { 0438 m_background->setCurrentAction(whiteAction); 0439 } else if (KdenliveSettings::monitor_background() == pinkAction->data().toString()) { 0440 m_background->setCurrentAction(pinkAction); 0441 } else { 0442 m_background->setCurrentAction(blackAction); 0443 } 0444 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 240, 0) 0445 connect(m_background, &KSelectAction::actionTriggered, this, [this](QAction *a) { 0446 #else 0447 connect(m_background, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, [this](QAction *a) { 0448 #endif 0449 KdenliveSettings::setMonitor_background(a->data().toString()); 0450 buildBackgroundedProducer(position()); 0451 }); 0452 } 0453 0454 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); 0455 setLayout(layout); 0456 setMinimumHeight(200); 0457 0458 connect(this, &Monitor::scopesClear, m_glMonitor, &VideoWidget::releaseAnalyse, Qt::DirectConnection); 0459 connect(m_glMonitor, &VideoWidget::analyseFrame, this, &Monitor::frameUpdated); 0460 m_timePos = new TimecodeDisplay(this); 0461 0462 if (id == Kdenlive::ProjectMonitor) { 0463 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::zoneUpdated); 0464 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZoneWithUndo, this, &Monitor::zoneUpdatedWithUndo); 0465 } else if (id == Kdenlive::ClipMonitor) { 0466 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::updateClipZone); 0467 } 0468 m_glMonitor->getControllerProxy()->setTimeCode(m_timePos); 0469 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::triggerAction, pCore.get(), &Core::triggerAction); 0470 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekNextKeyframe, this, &Monitor::seekToNextKeyframe); 0471 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekPreviousKeyframe, this, &Monitor::seekToPreviousKeyframe); 0472 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::addRemoveKeyframe, this, &Monitor::addRemoveKeyframe); 0473 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekToKeyframe, this, &Monitor::slotSeekToKeyFrame); 0474 0475 m_toolbar->addAction(manager->getAction(QStringLiteral("monitor_editmode"))); 0476 0477 m_toolbar->addSeparator(); 0478 m_toolbar->addWidget(m_timePos); 0479 m_toolbar->addAction(m_configMenuAction); 0480 m_toolbar->addSeparator(); 0481 QMargins mrg = m_toolbar->contentsMargins(); 0482 m_audioMeterWidget = new MonitorAudioLevel(m_toolbar->height() - mrg.top() - mrg.bottom(), this); 0483 m_toolbar->addWidget(m_audioMeterWidget); 0484 if (!m_audioMeterWidget->isValid) { 0485 KdenliveSettings::setMonitoraudio(0x01); 0486 m_audioMeterWidget->setVisibility(false); 0487 } else { 0488 m_audioMeterWidget->setVisibility((KdenliveSettings::monitoraudio() & m_id) != 0); 0489 if (id == Kdenlive::ProjectMonitor) { 0490 connect(m_audioMeterWidget, &MonitorAudioLevel::audioLevelsAvailable, pCore.get(), &Core::audioLevelsAvailable); 0491 } 0492 } 0493 0494 // Trimming tool bar buttons 0495 m_trimmingbar = new QToolBar(this); 0496 m_trimmingbar->setIconSize(iconSize); 0497 0498 m_trimmingOffset = new QLabel(); 0499 m_trimmingbar->addWidget(m_trimmingOffset); 0500 0501 m_fiveLess = new QAction(i18n("-5"), this); 0502 m_trimmingbar->addAction(m_fiveLess); 0503 connect(m_fiveLess, &QAction::triggered, this, [&]() { 0504 slotTrimmingPos(-5); 0505 pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(-5, true); 0506 }); 0507 m_oneLess = new QAction(i18n("-1"), this); 0508 m_trimmingbar->addAction(m_oneLess); 0509 connect(m_oneLess, &QAction::triggered, this, [&]() { 0510 slotTrimmingPos(-1); 0511 pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(-1, true); 0512 }); 0513 m_oneMore = new QAction(i18n("+1"), this); 0514 m_trimmingbar->addAction(m_oneMore); 0515 connect(m_oneMore, &QAction::triggered, this, [&]() { 0516 slotTrimmingPos(1); 0517 pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(1, true); 0518 }); 0519 m_fiveMore = new QAction(i18n("+5"), this); 0520 m_trimmingbar->addAction(m_fiveMore); 0521 connect(m_fiveMore, &QAction::triggered, this, [&]() { 0522 slotTrimmingPos(5); 0523 pCore->window()->getCurrentTimeline()->model()->requestSlipSelection(5, true); 0524 }); 0525 0526 connect(m_timePos, SIGNAL(timeCodeEditingFinished()), this, SLOT(slotSeek())); 0527 layout->addWidget(m_toolbar); 0528 if (m_recManager) { 0529 layout->addWidget(m_recManager->toolbar()); 0530 } 0531 layout->addWidget(m_trimmingbar); 0532 m_trimmingbar->setVisible(false); 0533 0534 // Load monitor overlay qml 0535 loadQmlScene(MonitorSceneDefault); 0536 0537 // Monitor dropped fps timer 0538 m_droppedTimer.setInterval(1000); 0539 m_droppedTimer.setSingleShot(false); 0540 connect(&m_droppedTimer, &QTimer::timeout, this, &Monitor::checkDrops); 0541 0542 // Info message widget 0543 m_infoMessage = new KMessageWidget(this); 0544 layout->addWidget(m_infoMessage); 0545 m_infoMessage->hide(); 0546 } 0547 0548 Monitor::~Monitor() 0549 { 0550 m_markerModel.reset(); 0551 delete m_audioMeterWidget; 0552 delete m_glMonitor; 0553 delete m_glWidget; 0554 delete m_timePos; 0555 } 0556 0557 void Monitor::setOffsetX(int x) 0558 { 0559 m_glMonitor->setOffsetX(x, m_horizontalScroll->maximum()); 0560 } 0561 0562 void Monitor::setOffsetY(int y) 0563 { 0564 m_glMonitor->setOffsetY(y, m_verticalScroll->maximum()); 0565 } 0566 0567 void Monitor::slotGetCurrentImage(bool request) 0568 { 0569 m_glMonitor->sendFrameForAnalysis = request; 0570 if (request) { 0571 slotActivateMonitor(); 0572 refreshMonitor(true); 0573 // Update analysis state 0574 QTimer::singleShot(500, m_monitorManager, &MonitorManager::checkScopes); 0575 } else { 0576 m_glMonitor->releaseAnalyse(); 0577 } 0578 } 0579 0580 void Monitor::refreshIcons() 0581 { 0582 QList<QAction *> allMenus = this->findChildren<QAction *>(); 0583 for (int i = 0; i < allMenus.count(); i++) { 0584 QAction *m = allMenus.at(i); 0585 QIcon ic = m->icon(); 0586 if (ic.isNull() || ic.name().isEmpty()) { 0587 continue; 0588 } 0589 QIcon newIcon = QIcon::fromTheme(ic.name()); 0590 m->setIcon(newIcon); 0591 } 0592 QList<KDualAction *> allButtons = this->findChildren<KDualAction *>(); 0593 for (int i = 0; i < allButtons.count(); i++) { 0594 KDualAction *m = allButtons.at(i); 0595 QIcon ic = m->activeIcon(); 0596 if (ic.isNull() || ic.name().isEmpty()) { 0597 continue; 0598 } 0599 QIcon newIcon = QIcon::fromTheme(ic.name()); 0600 m->setActiveIcon(newIcon); 0601 ic = m->inactiveIcon(); 0602 if (ic.isNull() || ic.name().isEmpty()) { 0603 continue; 0604 } 0605 newIcon = QIcon::fromTheme(ic.name()); 0606 m->setInactiveIcon(newIcon); 0607 } 0608 } 0609 0610 QAction *Monitor::recAction() 0611 { 0612 if (m_recManager) { 0613 return m_recManager->recAction(); 0614 } 0615 return nullptr; 0616 } 0617 0618 void Monitor::slotLockMonitor(bool lock) 0619 { 0620 m_monitorManager->lockMonitor(m_id, lock); 0621 } 0622 0623 void Monitor::setupMenu(QMenu *goMenu, QMenu *overlayMenu, QAction *playZone, QAction *loopZone, QMenu *markerMenu, QAction *loopClip) 0624 { 0625 delete m_contextMenu; 0626 m_contextMenu = new QMenu(this); 0627 m_contextMenu->addMenu(m_playMenu); 0628 if (goMenu) { 0629 m_contextMenu->addMenu(goMenu); 0630 } 0631 0632 if (markerMenu) { 0633 m_contextMenu->addMenu(markerMenu); 0634 QList<QAction *> list = markerMenu->actions(); 0635 for (int i = 0; i < list.count(); ++i) { 0636 if (list.at(i)->objectName() == QLatin1String("edit_marker")) { 0637 m_editMarker = list.at(i); 0638 break; 0639 } 0640 } 0641 } 0642 0643 m_playMenu->addAction(playZone); 0644 m_playMenu->addAction(loopZone); 0645 if (loopClip) { 0646 m_loopClipAction = loopClip; 0647 m_playMenu->addAction(loopClip); 0648 } 0649 0650 m_contextMenu->addAction(m_markerMenu); 0651 if (m_id == Kdenlive::ClipMonitor) { 0652 // m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save zone"), this, SLOT(slotSaveZone())); 0653 auto *extractZone = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Extract Zone"), this); 0654 connect(extractZone, &QAction::triggered, this, &Monitor::slotExtractCurrentZone); 0655 m_configMenuAction->addAction(extractZone); 0656 m_contextMenu->addAction(extractZone); 0657 m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("insert_project_tree"))); 0658 } 0659 m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame"))); 0660 m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame_to_project"))); 0661 m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("add_project_note"))); 0662 0663 m_contextMenu->addAction(m_markIn); 0664 m_contextMenu->addAction(m_markOut); 0665 QAction *setThumbFrame = 0666 m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Set current image as thumbnail"), this, SLOT(slotSetThumbFrame())); 0667 m_configMenuAction->addAction(setThumbFrame); 0668 if (m_id == Kdenlive::ProjectMonitor) { 0669 m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("monitor_multitrack"))); 0670 } else if (m_id == Kdenlive::ClipMonitor) { 0671 QAction *alwaysShowAudio = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-audiothumb")), i18n("Always show audio thumbnails"), this); 0672 alwaysShowAudio->setCheckable(true); 0673 connect(alwaysShowAudio, &QAction::triggered, this, [this](bool checked) { 0674 KdenliveSettings::setAlwaysShowMonitorAudio(checked); 0675 m_glMonitor->rootObject()->setProperty("permanentAudiothumb", checked); 0676 }); 0677 alwaysShowAudio->setChecked(KdenliveSettings::alwaysShowMonitorAudio()); 0678 m_contextMenu->addAction(alwaysShowAudio); 0679 m_configMenuAction->addAction(alwaysShowAudio); 0680 } 0681 0682 if (overlayMenu) { 0683 m_contextMenu->addMenu(overlayMenu); 0684 } 0685 0686 m_configMenuAction->addAction(m_monitorManager->getAction("mlt_scrub")); 0687 0688 QAction *switchAudioMonitor = new QAction(i18n("Show Audio Levels"), this); 0689 connect(switchAudioMonitor, &QAction::triggered, this, &Monitor::slotSwitchAudioMonitor); 0690 m_configMenuAction->addAction(switchAudioMonitor); 0691 switchAudioMonitor->setCheckable(true); 0692 switchAudioMonitor->setChecked((KdenliveSettings::monitoraudio() & m_id) != 0); 0693 0694 if (m_id == Kdenlive::ClipMonitor) { 0695 QAction *recordTimecode = new QAction(i18n("Show Source Timecode"), this); 0696 recordTimecode->setCheckable(true); 0697 connect(recordTimecode, &QAction::triggered, this, &Monitor::slotSwitchRecTimecode); 0698 recordTimecode->setChecked(KdenliveSettings::rectimecode()); 0699 m_configMenuAction->addAction(recordTimecode); 0700 } 0701 0702 // For some reason, the frame in QAbstracSpinBox (base class of TimeCodeDisplay) needs to be displayed once, then hidden 0703 // or it will never appear (supposed to appear on hover). 0704 m_timePos->setFrame(false); 0705 } 0706 0707 void Monitor::slotGoToMarker(QAction *action) 0708 { 0709 int pos = action->data().toInt(); 0710 slotSeek(pos); 0711 } 0712 0713 void Monitor::slotForceSize(QAction *a) 0714 { 0715 int resizeType = a->data().toInt(); 0716 int profileWidth = 320; 0717 int profileHeight = 200; 0718 if (resizeType > 0) { 0719 // calculate size 0720 QRect r = QApplication::primaryScreen()->geometry(); 0721 profileHeight = m_glMonitor->profileSize().height() * resizeType / 100; 0722 profileWidth = int(pCore->getCurrentProfile()->dar() * profileHeight); 0723 if (profileWidth > r.width() * 0.8 || profileHeight > r.height() * 0.7) { 0724 // reset action to free resize 0725 const QList<QAction *> list = m_forceSize->actions(); 0726 for (QAction *ac : list) { 0727 if (ac->data().toInt() == m_forceSizeFactor) { 0728 m_forceSize->setCurrentAction(ac); 0729 break; 0730 } 0731 } 0732 warningMessage(i18n("Your screen resolution is not sufficient for this action")); 0733 return; 0734 } 0735 } 0736 switch (resizeType) { 0737 case 100: 0738 case 50: 0739 // resize full size 0740 setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); 0741 profileHeight += m_glMonitor->m_displayRulerHeight; 0742 m_glMonitor->setMinimumSize(profileWidth, profileHeight); 0743 m_glMonitor->setMaximumSize(profileWidth, profileHeight); 0744 setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height())); 0745 break; 0746 default: 0747 // Free resize 0748 m_glMonitor->setMinimumSize(profileWidth, profileHeight); 0749 m_glMonitor->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); 0750 setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height() + m_glMonitor->getControllerProxy()->rulerHeight())); 0751 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); 0752 break; 0753 } 0754 m_forceSizeFactor = resizeType; 0755 updateGeometry(); 0756 } 0757 0758 void Monitor::buildBackgroundedProducer(int pos) 0759 { 0760 if (m_controller == nullptr) { 0761 return; 0762 } 0763 if (KdenliveSettings::monitor_background() != "black") { 0764 Mlt::Tractor trac(pCore->getProjectProfile()); 0765 QString color = QString("color:%1").arg(KdenliveSettings::monitor_background()); 0766 std::shared_ptr<Mlt::Producer> bg(new Mlt::Producer(*trac.profile(), color.toUtf8().constData())); 0767 int maxLength = m_controller->originalProducer()->get_length(); 0768 bg->set("length", maxLength); 0769 bg->set("out", maxLength - 1); 0770 bg->set("mlt_image_format", "rgba"); 0771 trac.set_track(*bg.get(), 0); 0772 trac.set_track(*m_controller->originalProducer().get(), 1); 0773 QString composite = TransitionsRepository::get()->getCompositingTransition(); 0774 std::unique_ptr<Mlt::Transition> transition = TransitionsRepository::get()->getTransition(composite); 0775 transition->set("always_active", 1); 0776 transition->set_tracks(0, 1); 0777 trac.plant_transition(*transition.get(), 0, 1); 0778 m_glMonitor->setProducer(std::make_shared<Mlt::Producer>(trac), isActive(), pos); 0779 } else { 0780 m_glMonitor->setProducer(m_controller->originalProducer(), isActive(), pos); 0781 } 0782 } 0783 0784 void Monitor::updateMarkers() 0785 { 0786 if (m_markerMenu) { 0787 // Fill guide menu 0788 m_markerMenu->menu()->clear(); 0789 std::shared_ptr<MarkerListModel> model; 0790 if (m_id == Kdenlive::ClipMonitor && m_controller) { 0791 model = m_controller->getMarkerModel(); 0792 } else if (m_id == Kdenlive::ProjectMonitor && pCore->currentDoc()) { 0793 model = pCore->currentDoc()->getGuideModel(pCore->currentTimelineId()); 0794 } 0795 if (model) { 0796 QList<CommentedTime> markersList = model->getAllMarkers(); 0797 for (const CommentedTime &mkr : qAsConst(markersList)) { 0798 QString label = pCore->timecode().getTimecode(mkr.time()) + QLatin1Char(' ') + mkr.comment(); 0799 QAction *a = new QAction(label); 0800 a->setData(mkr.time().frames(pCore->getCurrentFps())); 0801 m_markerMenu->addAction(a); 0802 } 0803 } 0804 m_markerMenu->setEnabled(!m_markerMenu->menu()->isEmpty()); 0805 } 0806 } 0807 0808 void Monitor::updateDocumentUuid() 0809 { 0810 m_glMonitor->rootObject()->setProperty("documentId", pCore->currentDoc()->uuid()); 0811 } 0812 0813 void Monitor::slotSeekToPreviousSnap() 0814 { 0815 if (m_controller) { 0816 m_glMonitor->getControllerProxy()->setPosition(getSnapForPos(true).frames(pCore->getCurrentFps())); 0817 } 0818 } 0819 0820 void Monitor::slotSeekToNextSnap() 0821 { 0822 if (m_controller) { 0823 m_glMonitor->getControllerProxy()->setPosition(getSnapForPos(false).frames(pCore->getCurrentFps())); 0824 } 0825 } 0826 0827 int Monitor::position() 0828 { 0829 return m_glMonitor->getControllerProxy()->getPosition(); 0830 } 0831 0832 GenTime Monitor::getSnapForPos(bool previous) 0833 { 0834 int frame = previous ? m_snaps->getPreviousPoint(m_glMonitor->getCurrentPos()) : m_snaps->getNextPoint(m_glMonitor->getCurrentPos()); 0835 return {frame, pCore->getCurrentFps()}; 0836 } 0837 0838 void Monitor::slotLoadClipZone(const QPoint &zone) 0839 { 0840 m_glMonitor->getControllerProxy()->setZone(zone.x(), zone.y(), false); 0841 Q_EMIT zoneDurationChanged(zone.y() - zone.x()); 0842 checkOverlay(); 0843 } 0844 0845 void Monitor::slotSetZoneStart() 0846 { 0847 QPoint oldZone = m_glMonitor->getControllerProxy()->zone(); 0848 int currentIn = m_glMonitor->getCurrentPos(); 0849 int updatedZoneOut = -1; 0850 if (currentIn > oldZone.y()) { 0851 updatedZoneOut = qMin(m_glMonitor->duration() - 1, currentIn + (oldZone.y() - oldZone.x())); 0852 } 0853 0854 Fun undo_zone = [this, oldZone, updatedZoneOut]() { 0855 m_glMonitor->getControllerProxy()->setZoneIn(oldZone.x()); 0856 if (updatedZoneOut > -1) { 0857 m_glMonitor->getControllerProxy()->setZoneOut(oldZone.y()); 0858 } 0859 const QPoint zone = m_glMonitor->getControllerProxy()->zone(); 0860 Q_EMIT zoneDurationChanged(zone.y() - zone.x()); 0861 checkOverlay(); 0862 return true; 0863 }; 0864 Fun redo_zone = [this, currentIn, updatedZoneOut]() { 0865 if (updatedZoneOut > -1) { 0866 m_glMonitor->getControllerProxy()->setZoneOut(updatedZoneOut); 0867 } 0868 m_glMonitor->getControllerProxy()->setZoneIn(currentIn); 0869 const QPoint zone = m_glMonitor->getControllerProxy()->zone(); 0870 Q_EMIT zoneDurationChanged(zone.y() - zone.x()); 0871 checkOverlay(); 0872 return true; 0873 }; 0874 redo_zone(); 0875 pCore->pushUndo(undo_zone, redo_zone, i18n("Set Zone")); 0876 } 0877 0878 void Monitor::slotSetZoneEnd() 0879 { 0880 QPoint oldZone = m_glMonitor->getControllerProxy()->zone(); 0881 int currentOut = m_glMonitor->getCurrentPos() + 1; 0882 int updatedZoneIn = -1; 0883 if (currentOut < oldZone.x()) { 0884 updatedZoneIn = qMax(0, currentOut - (oldZone.y() - oldZone.x())); 0885 } 0886 Fun undo_zone = [this, oldZone, updatedZoneIn]() { 0887 m_glMonitor->getControllerProxy()->setZoneOut(oldZone.y()); 0888 if (updatedZoneIn > -1) { 0889 m_glMonitor->getControllerProxy()->setZoneIn(oldZone.x()); 0890 } 0891 const QPoint zone = m_glMonitor->getControllerProxy()->zone(); 0892 Q_EMIT zoneDurationChanged(zone.y() - zone.x()); 0893 checkOverlay(); 0894 return true; 0895 }; 0896 0897 Fun redo_zone = [this, currentOut, updatedZoneIn]() { 0898 if (updatedZoneIn > -1) { 0899 m_glMonitor->getControllerProxy()->setZoneIn(updatedZoneIn); 0900 } 0901 m_glMonitor->getControllerProxy()->setZoneOut(currentOut); 0902 const QPoint zone = m_glMonitor->getControllerProxy()->zone(); 0903 Q_EMIT zoneDurationChanged(zone.y() - zone.x()); 0904 checkOverlay(); 0905 return true; 0906 }; 0907 redo_zone(); 0908 pCore->pushUndo(undo_zone, redo_zone, i18n("Set Zone")); 0909 } 0910 0911 // virtual 0912 void Monitor::mousePressEvent(QMouseEvent *event) 0913 { 0914 m_monitorManager->activateMonitor(m_id); 0915 if ((event->button() & Qt::RightButton) == 0u) { 0916 if (m_glWidget->geometry().contains(event->pos())) { 0917 m_DragStartPosition = event->pos(); 0918 event->accept(); 0919 } 0920 } else if (m_contextMenu) { 0921 slotActivateMonitor(); 0922 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0923 m_contextMenu->popup(event->globalPos()); 0924 #else 0925 m_contextMenu->popup(event->globalPosition().toPoint()); 0926 #endif 0927 event->accept(); 0928 } 0929 QWidget::mousePressEvent(event); 0930 } 0931 0932 void Monitor::slotShowMenu(const QPoint pos) 0933 { 0934 slotActivateMonitor(); 0935 if (m_contextMenu) { 0936 updateMarkers(); 0937 m_contextMenu->popup(pos); 0938 } 0939 } 0940 0941 void Monitor::resizeEvent(QResizeEvent *event) 0942 { 0943 Q_UNUSED(event) 0944 if (m_glMonitor->zoom() > 0.0f) { 0945 float horizontal = float(m_horizontalScroll->value()) / float(m_horizontalScroll->maximum()); 0946 float vertical = float(m_verticalScroll->value()) / float(m_verticalScroll->maximum()); 0947 adjustScrollBars(horizontal, vertical); 0948 } else { 0949 m_horizontalScroll->hide(); 0950 m_verticalScroll->hide(); 0951 } 0952 } 0953 0954 void Monitor::adjustScrollBars(float horizontal, float vertical) 0955 { 0956 if (m_glMonitor->zoom() > 1.0f) { 0957 m_horizontalScroll->setPageStep(m_glWidget->width()); 0958 m_horizontalScroll->setMaximum(int(m_glWidget->width() * m_glMonitor->zoom())); 0959 m_horizontalScroll->setValue(qRound(horizontal * float(m_horizontalScroll->maximum()))); 0960 Q_EMIT m_horizontalScroll->valueChanged(m_horizontalScroll->value()); 0961 m_horizontalScroll->show(); 0962 } else { 0963 Q_EMIT m_horizontalScroll->valueChanged(int(0.5f * m_glWidget->width() * m_glMonitor->zoom())); 0964 m_horizontalScroll->hide(); 0965 } 0966 0967 if (m_glMonitor->zoom() > 1.0f) { 0968 m_verticalScroll->setPageStep(m_glWidget->height()); 0969 m_verticalScroll->setMaximum(int(m_glWidget->height() * m_glMonitor->zoom())); 0970 m_verticalScroll->setValue(int(m_verticalScroll->maximum() * vertical)); 0971 Q_EMIT m_verticalScroll->valueChanged(m_verticalScroll->value()); 0972 m_verticalScroll->show(); 0973 } else { 0974 Q_EMIT m_verticalScroll->valueChanged(int(0.5f * m_glWidget->height() * m_glMonitor->zoom())); 0975 m_verticalScroll->hide(); 0976 } 0977 } 0978 0979 void Monitor::setZoom(float zoomRatio) 0980 { 0981 if (qFuzzyCompare(m_glMonitor->zoom(), 1.0f)) { 0982 adjustScrollBars(1., 1.); 0983 } else if (qFuzzyCompare(m_glMonitor->zoom() / zoomRatio, 1.0f)) { 0984 adjustScrollBars(0.5f, 0.5f); 0985 } else { 0986 float horizontal = float(m_horizontalScroll->value()) / float(m_horizontalScroll->maximum()); 0987 float vertical = float(m_verticalScroll->value()) / float(m_verticalScroll->maximum()); 0988 adjustScrollBars(horizontal, vertical); 0989 } 0990 } 0991 0992 bool Monitor::monitorIsFullScreen() const 0993 { 0994 return m_glWidget->isFullScreen(); 0995 } 0996 0997 void Monitor::slotSwitchFullScreen(bool minimizeOnly) 0998 { 0999 // TODO: disable screensaver? 1000 m_glMonitor->refreshZoom = true; 1001 if (!m_glWidget->isFullScreen() && !minimizeOnly) { 1002 // Move monitor widget to the second screen (one screen for Kdenlive, the other one for the Monitor widget) 1003 if (qApp->screens().count() > 1) { 1004 bool screenFound = false; 1005 int ix = -1; 1006 if (!KdenliveSettings::fullscreen_monitor().isEmpty()) { 1007 // If the platform does now provide screen serial number, use indexes 1008 for (const QScreen *screen : qApp->screens()) { 1009 ix++; 1010 bool match = KdenliveSettings::fullscreen_monitor() == QString("%1:%2").arg(QString::number(ix), screen->serialNumber()); 1011 // Check if monitor's index changed 1012 if (!match && !screen->serialNumber().isEmpty()) { 1013 match = KdenliveSettings::fullscreen_monitor().section(QLatin1Char(':'), 1) == screen->serialNumber(); 1014 } 1015 if (match) { 1016 // Match 1017 m_glWidget->setParent(nullptr); 1018 m_glWidget->move(screen->geometry().topLeft()); 1019 m_glWidget->resize(screen->geometry().size()); 1020 screenFound = true; 1021 break; 1022 } 1023 } 1024 } 1025 if (!screenFound) { 1026 for (const QScreen *screen : qApp->screens()) { 1027 // Autodetect second monitor 1028 QRect screenRect = screen->geometry(); 1029 if (!screenRect.contains(pCore->window()->geometry().center())) { 1030 m_glWidget->setParent(nullptr); 1031 m_glWidget->move(screenRect.topLeft()); 1032 m_glWidget->resize(screenRect.size()); 1033 screenFound = true; 1034 break; 1035 } 1036 } 1037 } 1038 if (!screenFound) { 1039 m_glWidget->setParent(nullptr); 1040 } 1041 } else { 1042 m_glWidget->setParent(nullptr); 1043 } 1044 m_glWidget->showFullScreen(); 1045 setFocus(); 1046 } else { 1047 m_glWidget->showNormal(); 1048 auto *lay = static_cast<QVBoxLayout *>(layout()); 1049 lay->insertWidget(0, m_glWidget, 10); 1050 // With some Qt versions, focus was lost after switching back from fullscreen, 1051 // QApplication::setActiveWindow restores focus to the correct window 1052 QApplication::setActiveWindow(this); 1053 setFocus(); 1054 } 1055 } 1056 1057 void Monitor::fixFocus() 1058 { 1059 setFocus(); 1060 } 1061 1062 // virtual 1063 void Monitor::mouseReleaseEvent(QMouseEvent *event) 1064 { 1065 if (m_dragStarted) { 1066 event->ignore(); 1067 QWidget::mouseReleaseEvent(event); 1068 return; 1069 } 1070 if (event->button() != Qt::RightButton) { 1071 if (m_glMonitor->geometry().contains(event->pos())) { 1072 if (isActive()) { 1073 slotPlay(); 1074 } else { 1075 slotActivateMonitor(); 1076 } 1077 } // else event->ignore(); //QWidget::mouseReleaseEvent(event); 1078 } 1079 m_dragStarted = false; 1080 event->accept(); 1081 QWidget::mouseReleaseEvent(event); 1082 } 1083 1084 void Monitor::slotStartDrag() 1085 { 1086 if (m_id == Kdenlive::ProjectMonitor || m_controller == nullptr) { 1087 // dragging is only allowed for clip monitor 1088 return; 1089 } 1090 auto *drag = new QDrag(this); 1091 auto *mimeData = new QMimeData; 1092 QByteArray prodData; 1093 QPoint p = m_glMonitor->getControllerProxy()->zone(); 1094 if (p.x() == -1 || p.y() == -1) { 1095 prodData = m_controller->AbstractProjectItem::clipId().toUtf8(); 1096 } else { 1097 QStringList list; 1098 list.append(m_controller->AbstractProjectItem::clipId()); 1099 list.append(QString::number(p.x())); 1100 list.append(QString::number(p.y() - 1)); 1101 prodData.append(list.join(QLatin1Char('/')).toUtf8()); 1102 } 1103 mimeData->setData(QStringLiteral("text/producerslist"), prodData); 1104 mimeData->setData(QStringLiteral("text/dragid"), QUuid::createUuid().toByteArray()); 1105 drag->setMimeData(mimeData); 1106 drag->exec(Qt::CopyAction); 1107 Q_EMIT pCore->bin()->processDragEnd(); 1108 } 1109 1110 // virtual 1111 void Monitor::wheelEvent(QWheelEvent *event) 1112 { 1113 slotMouseSeek(event->angleDelta().y(), event->modifiers()); 1114 event->accept(); 1115 } 1116 1117 void Monitor::mouseDoubleClickEvent(QMouseEvent *event) 1118 { 1119 event->accept(); 1120 slotSwitchFullScreen(); 1121 } 1122 1123 void Monitor::keyPressEvent(QKeyEvent *event) 1124 { 1125 if (event->key() == Qt::Key_Escape) { 1126 slotSwitchFullScreen(); 1127 event->accept(); 1128 return; 1129 } 1130 if (m_glWidget->isFullScreen()) { 1131 event->ignore(); 1132 Q_EMIT passKeyPress(event); 1133 return; 1134 } 1135 QWidget::keyPressEvent(event); 1136 } 1137 1138 void Monitor::slotMouseSeek(int eventDelta, uint modifiers) 1139 { 1140 if ((modifiers & Qt::ControlModifier) != 0u) { 1141 // Ctrl wheel zooms monitor 1142 m_glMonitor->slotZoom(eventDelta > 0); 1143 return; 1144 } else if ((modifiers & Qt::ShiftModifier) != 0u) { 1145 // Shift wheel seeks one second 1146 int delta = qRound(pCore->getCurrentFps()); 1147 if (eventDelta > 0) { 1148 delta = -delta; 1149 } 1150 delta = qBound(0, m_glMonitor->getCurrentPos() + delta, m_glMonitor->duration() - 1); 1151 m_glMonitor->getControllerProxy()->setPosition(delta); 1152 } else if ((modifiers & Qt::AltModifier) != 0u) { 1153 if (eventDelta >= 0) { 1154 Q_EMIT seekToPreviousSnap(); 1155 } else { 1156 Q_EMIT seekToNextSnap(); 1157 } 1158 } else { 1159 if (eventDelta >= 0) { 1160 slotRewindOneFrame(); 1161 } else { 1162 slotForwardOneFrame(); 1163 } 1164 } 1165 } 1166 1167 void Monitor::slotSetThumbFrame() 1168 { 1169 pCore->setDocumentModified(); 1170 if (m_controller == nullptr || m_controller->clipType() == ClipType::Timeline) { 1171 // This is a sequence thumbnail 1172 pCore->bin()->setSequenceThumbnail(pCore->currentTimelineId(), m_glMonitor->getCurrentPos()); 1173 return; 1174 } 1175 m_controller->setProducerProperty(QStringLiteral("kdenlive:thumbnailFrame"), m_glMonitor->getCurrentPos()); 1176 Q_EMIT refreshClipThumbnail(m_controller->AbstractProjectItem::clipId()); 1177 } 1178 1179 void Monitor::slotExtractCurrentZone() 1180 { 1181 if (m_controller == nullptr) { 1182 return; 1183 } 1184 CutTask::start(ObjectId(KdenliveObjectType::BinClip, m_controller->clipId().toInt(), QUuid()), getZoneStart(), getZoneEnd(), this); 1185 } 1186 1187 std::shared_ptr<ProjectClip> Monitor::currentController() const 1188 { 1189 return m_controller; 1190 } 1191 1192 void Monitor::slotExtractCurrentFrame(QString frameName, bool addToProject) 1193 { 1194 if (m_playAction->isActive()) { 1195 // Pause playing 1196 switchPlay(false); 1197 } 1198 if (QFileInfo(frameName).fileName().isEmpty()) { 1199 // convenience: when extracting an image to be added to the project, 1200 // suggest a suitable image file name. In the project monitor, this 1201 // suggestion bases on the project file name; in the clip monitor, 1202 // the suggestion bases on the clip file name currently shown. 1203 // Finally, the frame number is added to this suggestion, prefixed 1204 // with "-f", so we get something like clip-f#.png. 1205 QString suggestedImageName = 1206 QFileInfo(currentController() ? currentController()->clipName() 1207 : pCore->currentDoc()->url().isValid() ? pCore->currentDoc()->url().fileName() : i18n("untitled")) 1208 .completeBaseName() + 1209 QStringLiteral("-f") + QString::number(m_glMonitor->getCurrentPos()).rightJustified(6, QLatin1Char('0')) + QStringLiteral(".png"); 1210 frameName = QFileInfo(frameName, suggestedImageName).fileName(); 1211 } 1212 1213 QString framesFolder = KRecentDirs::dir(QStringLiteral(":KdenliveFramesFolder")); 1214 if (framesFolder.isEmpty()) { 1215 framesFolder = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); 1216 } 1217 QScopedPointer<QDialog> dlg(new QDialog(this)); 1218 QScopedPointer<KFileWidget> fileWidget(new KFileWidget(QUrl::fromLocalFile(framesFolder), dlg.data())); 1219 dlg->setWindowTitle(addToProject ? i18nc("@title:window", "Save Image to Project") : i18nc("@title:window", "Save Image")); 1220 auto *layout = new QVBoxLayout; 1221 layout->addWidget(fileWidget.data()); 1222 QCheckBox *b = nullptr; 1223 if (m_id == Kdenlive::ClipMonitor && m_controller && m_controller->clipType() != ClipType::Text) { 1224 QSize fSize = m_controller->getFrameSize(); 1225 if (fSize != pCore->getCurrentFrameSize()) { 1226 b = new QCheckBox(i18n("Export image using source resolution"), dlg.data()); 1227 b->setChecked(KdenliveSettings::exportframe_usingsourceres()); 1228 fileWidget->setCustomWidget(b); 1229 } 1230 } 1231 fileWidget->setConfirmOverwrite(true); 1232 fileWidget->okButton()->show(); 1233 fileWidget->cancelButton()->show(); 1234 QObject::connect(fileWidget->okButton(), &QPushButton::clicked, fileWidget.data(), &KFileWidget::slotOk); 1235 QObject::connect(fileWidget.data(), &KFileWidget::accepted, fileWidget.data(), &KFileWidget::accept); 1236 QObject::connect(fileWidget.data(), &KFileWidget::accepted, dlg.data(), &QDialog::accept); 1237 QObject::connect(fileWidget->cancelButton(), &QPushButton::clicked, dlg.data(), &QDialog::reject); 1238 dlg->setLayout(layout); 1239 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1240 fileWidget->setMimeFilter(QStringList() << QStringLiteral("image/png")); 1241 #else 1242 fileWidget->setFilters({KFileFilter::fromMimeType(QStringLiteral("image/png"))}); 1243 #endif 1244 fileWidget->setMode(KFile::File | KFile::LocalOnly); 1245 fileWidget->setOperationMode(KFileWidget::Saving); 1246 QUrl relativeUrl; 1247 relativeUrl.setPath(frameName); 1248 fileWidget->setSelectedUrl(relativeUrl); 1249 KSharedConfig::Ptr conf = KSharedConfig::openConfig(); 1250 QWindow *handle = dlg->windowHandle(); 1251 if ((handle != nullptr) && conf->hasGroup("FileDialogSize")) { 1252 KWindowConfig::restoreWindowSize(handle, conf->group("FileDialogSize")); 1253 dlg->resize(handle->size()); 1254 } 1255 if (dlg->exec() == QDialog::Accepted) { 1256 QString selectedFile = fileWidget->selectedFile(); 1257 bool useSourceResolution = b != nullptr && b->isChecked(); 1258 if (!selectedFile.isEmpty()) { 1259 if (b != nullptr) { 1260 KdenliveSettings::setExportframe_usingsourceres(useSourceResolution); 1261 } 1262 KRecentDirs::add(QStringLiteral(":KdenliveFramesFolder"), QUrl::fromLocalFile(selectedFile).adjusted(QUrl::RemoveFilename).toLocalFile()); 1263 // check if we are using a proxy 1264 if ((m_controller != nullptr) && !m_controller->getProducerProperty(QStringLiteral("kdenlive:proxy")).isEmpty() && 1265 m_controller->getProducerProperty(QStringLiteral("kdenlive:proxy")) != QLatin1String("-")) { 1266 // Clip monitor, using proxy. Use original clip url to get frame 1267 QTemporaryFile src(QDir::temp().absoluteFilePath(QString("XXXXXX.mlt"))); 1268 if (src.open()) { 1269 src.setAutoRemove(false); 1270 m_controller->cloneProducerToFile(src.fileName()); 1271 const QStringList pathInfo = {src.fileName(), selectedFile, pCore->bin()->getCurrentFolder()}; 1272 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1273 QtConcurrent::run(m_glMonitor->getControllerProxy(), &MonitorProxy::extractFrameToFile, m_glMonitor->getCurrentPos(), pathInfo, 1274 addToProject, useSourceResolution); 1275 #else 1276 QtConcurrent::run(&MonitorProxy::extractFrameToFile, m_glMonitor->getControllerProxy(), m_glMonitor->getCurrentPos(), pathInfo, 1277 addToProject, useSourceResolution); 1278 #endif 1279 } else { 1280 // TODO: warn user, cannot open tmp file 1281 qDebug() << "Could not create temporary file"; 1282 } 1283 return; 1284 } else { 1285 if (m_id == Kdenlive::ProjectMonitor) { 1286 // Create QImage with frame 1287 QImage frame; 1288 // Disable monitor preview scaling if any 1289 int previewScale = KdenliveSettings::previewScaling(); 1290 if (previewScale > 0) { 1291 KdenliveSettings::setPreviewScaling(0); 1292 m_glMonitor->updateScaling(); 1293 } 1294 // Check if we have proxied clips at position 1295 QStringList proxiedClips = pCore->window()->getCurrentTimeline()->model()->getProxiesAt(m_glMonitor->getCurrentPos()); 1296 // Temporarily disable proxy on those clips 1297 QMap<QString, QString> existingProxies; 1298 if (!proxiedClips.isEmpty()) { 1299 existingProxies = pCore->currentDoc()->proxyClipsById(proxiedClips, false); 1300 } 1301 disconnect(m_glMonitor, &VideoWidget::analyseFrame, this, &Monitor::frameUpdated); 1302 bool analysisStatus = m_glMonitor->sendFrameForAnalysis; 1303 m_glMonitor->sendFrameForAnalysis = true; 1304 if (m_captureConnection) { 1305 QObject::disconnect(m_captureConnection); 1306 } 1307 m_captureConnection = 1308 connect(m_glMonitor, &VideoWidget::analyseFrame, this, 1309 [this, proxiedClips, selectedFile, existingProxies, addToProject, analysisStatus, previewScale](const QImage &img) { 1310 m_glMonitor->sendFrameForAnalysis = analysisStatus; 1311 m_glMonitor->releaseAnalyse(); 1312 if (pCore->getCurrentSar() != 1.) { 1313 QImage scaled = img.scaled(pCore->getCurrentFrameDisplaySize()); 1314 scaled.save(selectedFile); 1315 } else { 1316 img.save(selectedFile); 1317 } 1318 if (previewScale > 0) { 1319 KdenliveSettings::setPreviewScaling(previewScale); 1320 m_glMonitor->updateScaling(); 1321 } 1322 // Re-enable proxy on those clips 1323 if (!proxiedClips.isEmpty()) { 1324 pCore->currentDoc()->proxyClipsById(proxiedClips, true, existingProxies); 1325 } 1326 QObject::disconnect(m_captureConnection); 1327 connect(m_glMonitor, &VideoWidget::analyseFrame, this, &Monitor::frameUpdated); 1328 KRecentDirs::add(QStringLiteral(":KdenliveFramesFolder"), 1329 QUrl::fromLocalFile(selectedFile).adjusted(QUrl::RemoveFilename).toLocalFile()); 1330 if (addToProject) { 1331 QString folderInfo = pCore->bin()->getCurrentFolder(); 1332 QMetaObject::invokeMethod(pCore->bin(), "droppedUrls", Qt::QueuedConnection, 1333 Q_ARG(QList<QUrl>, {QUrl::fromLocalFile(selectedFile)}), Q_ARG(QString, folderInfo)); 1334 } 1335 }); 1336 if (proxiedClips.isEmpty()) { 1337 // If there is a proxy, replacing it in timeline will trigger the monitor once replaced 1338 refreshMonitor(); 1339 } 1340 return; 1341 } else { 1342 QStringList pathInfo; 1343 if (useSourceResolution) { 1344 // Create a producer with the original clip 1345 QTemporaryFile src(QDir::temp().absoluteFilePath(QString("XXXXXX.mlt"))); 1346 if (src.open()) { 1347 src.setAutoRemove(false); 1348 m_controller->cloneProducerToFile(src.fileName()); 1349 pathInfo = QStringList({src.fileName(), selectedFile, pCore->bin()->getCurrentFolder()}); 1350 } 1351 } else { 1352 pathInfo = QStringList({QString(), selectedFile, pCore->bin()->getCurrentFolder()}); 1353 } 1354 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1355 QtConcurrent::run(m_glMonitor->getControllerProxy(), &MonitorProxy::extractFrameToFile, m_glMonitor->getCurrentPos(), pathInfo, 1356 addToProject, useSourceResolution); 1357 #else 1358 QtConcurrent::run(&MonitorProxy::extractFrameToFile, m_glMonitor->getControllerProxy(), m_glMonitor->getCurrentPos(), pathInfo, 1359 addToProject, useSourceResolution); 1360 #endif 1361 } 1362 } 1363 } 1364 } 1365 } 1366 1367 void Monitor::setTimePos(const QString &pos) 1368 { 1369 m_timePos->setValue(pos); 1370 slotSeek(); 1371 } 1372 1373 void Monitor::slotSeek() 1374 { 1375 slotSeek(m_timePos->getValue()); 1376 } 1377 1378 void Monitor::slotSeek(int pos) 1379 { 1380 if (!slotActivateMonitor()) { 1381 return; 1382 } 1383 m_glMonitor->getControllerProxy()->setPosition(pos); 1384 Q_EMIT m_monitorManager->cleanMixer(); 1385 } 1386 1387 void Monitor::refreshAudioThumbs() 1388 { 1389 Q_EMIT m_glMonitor->getControllerProxy()->audioThumbFormatChanged(); 1390 Q_EMIT m_glMonitor->getControllerProxy()->colorsChanged(); 1391 } 1392 1393 void Monitor::normalizeAudioThumbs() 1394 { 1395 Q_EMIT m_glMonitor->getControllerProxy()->audioThumbNormalizeChanged(); 1396 } 1397 1398 void Monitor::checkOverlay(int pos) 1399 { 1400 if (m_qmlManager->sceneType() != MonitorSceneDefault) { 1401 // we are not in main view, ignore 1402 return; 1403 } 1404 QString overlayText; 1405 QColor color; 1406 if (pos == -1) { 1407 pos = m_timePos->getValue(); 1408 } 1409 1410 if (m_markerModel) { 1411 int mid = m_markerModel->markerIdAtFrame(pos); 1412 if (mid > -1) { 1413 CommentedTime marker = m_markerModel->markerById(mid); 1414 overlayText = marker.comment(); 1415 color = pCore->markerTypes.value(marker.markerType()).color; 1416 } 1417 } 1418 m_glMonitor->getControllerProxy()->setMarker(overlayText, color); 1419 } 1420 1421 int Monitor::getZoneStart() 1422 { 1423 return m_glMonitor->getControllerProxy()->zoneIn(); 1424 } 1425 1426 int Monitor::getZoneEnd() 1427 { 1428 return m_glMonitor->getControllerProxy()->zoneOut(); 1429 } 1430 1431 void Monitor::slotZoneStart() 1432 { 1433 if (!slotActivateMonitor()) { 1434 return; 1435 } 1436 m_glMonitor->getControllerProxy()->setPosition(m_glMonitor->getControllerProxy()->zoneIn()); 1437 } 1438 1439 void Monitor::slotZoneEnd() 1440 { 1441 if (!slotActivateMonitor()) { 1442 return; 1443 } 1444 m_glMonitor->getControllerProxy()->setPosition(m_glMonitor->getControllerProxy()->zoneOut()); 1445 } 1446 1447 void Monitor::slotRewind(double speed) 1448 { 1449 if (!slotActivateMonitor() || m_trimmingbar->isVisible()) { 1450 return; 1451 } 1452 if (qFuzzyIsNull(speed)) { 1453 double currentspeed = m_glMonitor->playSpeed(); 1454 if (currentspeed > -1) { 1455 m_glMonitor->purgeCache(); 1456 speed = -1; 1457 m_speedIndex = 0; 1458 } else { 1459 m_speedIndex++; 1460 if (m_speedIndex > 5) { 1461 m_speedIndex = 0; 1462 } 1463 speed = -MonitorManager::speedArray[m_speedIndex]; 1464 } 1465 } 1466 updatePlayAction(true); 1467 m_glMonitor->switchPlay(true, speed); 1468 } 1469 1470 void Monitor::slotForward(double speed, bool allowNormalPlay) 1471 { 1472 if (!slotActivateMonitor() || m_trimmingbar->isVisible()) { 1473 return; 1474 } 1475 if (qFuzzyIsNull(speed)) { 1476 double currentspeed = m_glMonitor->playSpeed(); 1477 if (currentspeed < 1) { 1478 m_speedIndex = 0; 1479 if (allowNormalPlay) { 1480 m_glMonitor->purgeCache(); 1481 updatePlayAction(true); 1482 m_glMonitor->switchPlay(true); 1483 return; 1484 } 1485 } else { 1486 m_speedIndex++; 1487 } 1488 if (m_speedIndex > 5) { 1489 m_speedIndex = 0; 1490 } 1491 speed = MonitorManager::speedArray[m_speedIndex]; 1492 } 1493 updatePlayAction(true); 1494 m_glMonitor->switchPlay(true, speed); 1495 } 1496 1497 void Monitor::slotRewindOneFrame(int diff) 1498 { 1499 if (!slotActivateMonitor()) { 1500 return; 1501 } 1502 m_glMonitor->getControllerProxy()->setPosition(qMax(0, m_glMonitor->getCurrentPos() - diff)); 1503 } 1504 1505 void Monitor::slotForwardOneFrame(int diff) 1506 { 1507 if (!slotActivateMonitor()) { 1508 return; 1509 } 1510 if (m_id == Kdenlive::ClipMonitor) { 1511 m_glMonitor->getControllerProxy()->setPosition(qMin(m_glMonitor->duration() - 1, m_glMonitor->getCurrentPos() + diff)); 1512 } else { 1513 m_glMonitor->getControllerProxy()->setPosition(m_glMonitor->getCurrentPos() + diff); 1514 } 1515 } 1516 1517 void Monitor::adjustRulerSize(int length, const std::shared_ptr<MarkerSortModel> &markerModel) 1518 { 1519 if (m_controller != nullptr) { 1520 m_glMonitor->setRulerInfo(length); 1521 } else { 1522 m_glMonitor->setRulerInfo(length, markerModel); 1523 } 1524 m_timePos->setRange(0, length); 1525 1526 if (markerModel) { 1527 QAbstractItemModel *sourceModel = markerModel->sourceModel(); 1528 connect(sourceModel, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector<int>)), this, SLOT(checkOverlay())); 1529 connect(sourceModel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(checkOverlay())); 1530 connect(sourceModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(checkOverlay())); 1531 } else { 1532 // Project simply changed length, update display 1533 Q_EMIT durationChanged(length); 1534 } 1535 } 1536 1537 void Monitor::stop() 1538 { 1539 updatePlayAction(false); 1540 m_glMonitor->stop(); 1541 } 1542 1543 void Monitor::mute(bool mute) 1544 { 1545 // TODO: we should set the "audio_off" property to 1 to mute the consumer instead of changing volume 1546 m_glMonitor->setVolume(mute ? 0 : KdenliveSettings::volume() / 100.0); 1547 } 1548 1549 void Monitor::start() 1550 { 1551 if (!isVisible() || !isActive()) { 1552 return; 1553 } 1554 m_glMonitor->startConsumer(); 1555 } 1556 1557 void Monitor::slotRefreshMonitor(bool visible) 1558 { 1559 if (visible && monitorVisible()) { 1560 if (slotActivateMonitor()) { 1561 start(); 1562 } 1563 } 1564 } 1565 1566 void Monitor::forceMonitorRefresh() 1567 { 1568 if (!slotActivateMonitor()) { 1569 return; 1570 } 1571 m_glMonitor->refresh(); 1572 } 1573 1574 void Monitor::refreshMonitor(bool directUpdate) 1575 { 1576 if (!m_glMonitor->isReady() || isPlaying()) { 1577 return; 1578 } 1579 if (isActive()) { 1580 if (directUpdate) { 1581 m_glMonitor->refresh(); 1582 } else { 1583 m_glMonitor->requestRefresh(); 1584 } 1585 } else if (monitorVisible()) { 1586 // Monitor was not active. Check if the other one is visible to re-activate it afterwards 1587 bool otherMonitorVisible = m_id == Kdenlive::ClipMonitor ? m_monitorManager->projectMonitorVisible() : m_monitorManager->clipMonitorVisible(); 1588 slotActivateMonitor(); 1589 if (isActive()) { 1590 m_glMonitor->refresh(); 1591 // Monitor was not active, so we activate it, refresh and activate the other monitor once done 1592 QObject::disconnect(m_switchConnection); 1593 m_switchConnection = connect(m_glMonitor, &VideoWidget::frameDisplayed, this, [=]() { 1594 m_monitorManager->activateMonitor(m_id == Kdenlive::ClipMonitor ? Kdenlive::ProjectMonitor : Kdenlive::ClipMonitor, otherMonitorVisible); 1595 QObject::disconnect(m_switchConnection); 1596 }); 1597 } 1598 } 1599 } 1600 1601 bool Monitor::monitorVisible() const 1602 { 1603 return m_glWidget->isFullScreen() || !m_glWidget->visibleRegion().isEmpty(); 1604 } 1605 1606 void Monitor::refreshMonitorIfActive(bool directUpdate) 1607 { 1608 if (!m_glMonitor->isReady() || !isActive()) { 1609 return; 1610 } 1611 if (directUpdate) { 1612 m_glMonitor->refresh(); 1613 } else { 1614 m_glMonitor->requestRefresh(); 1615 } 1616 } 1617 1618 void Monitor::pause() 1619 { 1620 if (!m_playAction->isActive() || !slotActivateMonitor()) { 1621 return; 1622 } 1623 switchPlay(false); 1624 } 1625 1626 void Monitor::switchPlay(bool play) 1627 { 1628 if (m_trimmingbar->isVisible()) { 1629 return; 1630 } 1631 m_speedIndex = 0; 1632 if (!play) { 1633 m_droppedTimer.stop(); 1634 } 1635 if (!KdenliveSettings::autoscroll()) { 1636 Q_EMIT pCore->autoScrollChanged(); 1637 } 1638 if (!m_glMonitor->switchPlay(play)) { 1639 play = false; 1640 } 1641 m_playAction->setActive(play); 1642 } 1643 1644 void Monitor::updatePlayAction(bool play) 1645 { 1646 m_playAction->setActive(play); 1647 if (!play) { 1648 m_droppedTimer.stop(); 1649 } 1650 if (!KdenliveSettings::autoscroll()) { 1651 Q_EMIT pCore->autoScrollChanged(); 1652 } 1653 } 1654 1655 void Monitor::slotSwitchPlay() 1656 { 1657 if (!slotActivateMonitor() || m_trimmingbar->isVisible()) { 1658 return; 1659 } 1660 if (!KdenliveSettings::autoscroll()) { 1661 Q_EMIT pCore->autoScrollChanged(); 1662 } 1663 m_speedIndex = 0; 1664 bool play = m_playAction->isActive(); 1665 if (pCore->getAudioDevice()->isRecording()) { 1666 int recState = pCore->getAudioDevice()->recordState(); 1667 if (recState == QMediaRecorder::RecordingState) { 1668 if (!play) { 1669 pCore->getAudioDevice()->pauseRecording(); 1670 } 1671 } else if (recState == QMediaRecorder::PausedState && play) { 1672 pCore->getAudioDevice()->resumeRecording(); 1673 } 1674 m_displayingCountdown = true; 1675 } else if (pCore->getAudioDevice()->isMonitoring()) { 1676 if (m_displayingCountdown || KdenliveSettings::disablereccountdown()) { 1677 m_displayingCountdown = false; 1678 m_playAction->setActive(false); 1679 pCore->recordAudio(-1, true); 1680 return; 1681 } 1682 pCore->recordAudio(-1, true); 1683 } 1684 if (!m_glMonitor->switchPlay(play)) { 1685 play = false; 1686 m_playAction->setActive(false); 1687 } 1688 bool showDropped = false; 1689 if (m_id == Kdenlive::ClipMonitor) { 1690 showDropped = KdenliveSettings::displayClipMonitorInfo() & 0x20; 1691 } else if (m_id == Kdenlive::ProjectMonitor) { 1692 showDropped = KdenliveSettings::displayProjectMonitorInfo() & 0x20; 1693 } 1694 if (showDropped) { 1695 m_glMonitor->resetDrops(); 1696 if (play) { 1697 m_droppedTimer.start(); 1698 } else { 1699 m_droppedTimer.stop(); 1700 } 1701 } else { 1702 m_droppedTimer.stop(); 1703 } 1704 } 1705 1706 void Monitor::slotPlay() 1707 { 1708 m_playAction->trigger(); 1709 } 1710 1711 bool Monitor::isPlaying() const 1712 { 1713 return m_playAction->isActive(); 1714 } 1715 1716 void Monitor::resetPlayOrLoopZone(const QString &binId) 1717 { 1718 if (activeClipId() == binId) { 1719 m_glMonitor->resetZoneMode(); 1720 } 1721 } 1722 1723 void Monitor::slotPlayZone() 1724 { 1725 if (!slotActivateMonitor()) { 1726 return; 1727 } 1728 bool ok = m_glMonitor->playZone(); 1729 if (ok) { 1730 updatePlayAction(true); 1731 } 1732 } 1733 1734 void Monitor::slotLoopZone() 1735 { 1736 if (!slotActivateMonitor()) { 1737 return; 1738 } 1739 bool ok = m_glMonitor->playZone(true); 1740 if (ok) { 1741 updatePlayAction(true); 1742 } 1743 } 1744 1745 void Monitor::slotLoopClip(QPoint inOut) 1746 { 1747 if (!slotActivateMonitor()) { 1748 return; 1749 } 1750 bool ok = m_glMonitor->loopClip(inOut); 1751 if (ok) { 1752 updatePlayAction(true); 1753 } 1754 } 1755 1756 void Monitor::updateClipProducer(const std::shared_ptr<Mlt::Producer> &prod) 1757 { 1758 if (m_glMonitor->setProducer(prod, isActive(), -1)) { 1759 prod->set_speed(1.0); 1760 } 1761 } 1762 1763 void Monitor::updateClipProducer(const QString &playlist) 1764 { 1765 Q_UNUSED(playlist) 1766 // TODO 1767 // Mlt::Producer *prod = new Mlt::Producer(*m_glMonitor->profile(), playlist.toUtf8().constData()); 1768 // m_glMonitor->setProducer(prod, isActive(), render->seekFramePosition()); 1769 m_glMonitor->switchPlay(true); 1770 } 1771 1772 void Monitor::slotOpenClip(const std::shared_ptr<ProjectClip> &controller, int in, int out) 1773 { 1774 if (m_controller) { 1775 m_glMonitor->resetZoneMode(); 1776 // store last audiothumb zoom / position 1777 double zoomFactor = m_glMonitor->rootObject()->property("zoomFactor").toDouble(); 1778 if (zoomFactor != 1.) { 1779 double zoomStart = m_glMonitor->rootObject()->property("zoomStart").toDouble(); 1780 m_controller->setProducerProperty(QStringLiteral("kdenlive:thumbZoomFactor"), zoomFactor); 1781 m_controller->setProducerProperty(QStringLiteral("kdenlive:thumbZoomStart"), zoomStart); 1782 } else { 1783 m_controller->resetProducerProperty(QStringLiteral("kdenlive:thumbZoomFactor")); 1784 m_controller->resetProducerProperty(QStringLiteral("kdenlive:thumbZoomStart")); 1785 } 1786 m_controller->setProducerProperty(QStringLiteral("kdenlive:monitorPosition"), position()); 1787 disconnect(m_controller.get(), &ProjectClip::audioThumbReady, this, &Monitor::prepareAudioThumb); 1788 disconnect(m_controller->getMarkerModel().get(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector<int> &)), this, 1789 SLOT(checkOverlay())); 1790 disconnect(m_controller->getMarkerModel().get(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(checkOverlay())); 1791 disconnect(m_controller->getMarkerModel().get(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(checkOverlay())); 1792 if (m_controller->hasLimitedDuration()) { 1793 disconnect(m_controller.get(), &ProjectClip::boundsChanged, m_glMonitor->getControllerProxy(), &MonitorProxy::updateClipBounds); 1794 disconnect(m_controller.get(), &ProjectClip::registeredClipChanged, m_controller.get(), &ProjectClip::checkClipBounds); 1795 } 1796 } else if (controller == nullptr) { 1797 // Nothing to do 1798 pCore->taskManager.displayedClip = -1; 1799 return; 1800 } 1801 disconnect(this, &Monitor::seekPosition, this, &Monitor::seekRemap); 1802 m_controller = controller; 1803 pCore->taskManager.displayedClip = m_controller ? m_controller->clipId().toInt() : -1; 1804 m_glMonitor->getControllerProxy()->setAudioStream(QString()); 1805 m_snaps.reset(new SnapModel()); 1806 m_glMonitor->getControllerProxy()->resetZone(); 1807 if (controller) { 1808 m_markerModel = m_controller->getMarkerModel(); 1809 if (pCore->currentRemap(controller->clipId())) { 1810 connect(this, &Monitor::seekPosition, this, &Monitor::seekRemap, Qt::UniqueConnection); 1811 } 1812 ClipType::ProducerType type = controller->clipType(); 1813 if (type == ClipType::AV || type == ClipType::Video || type == ClipType::SlideShow) { 1814 m_glMonitor->rootObject()->setProperty("baseThumbPath", 1815 QString("image://thumbnail/%1/%2/#").arg(controller->clipId(), pCore->currentDoc()->uuid().toString())); 1816 } else { 1817 m_glMonitor->rootObject()->setProperty("baseThumbPath", QString()); 1818 } 1819 m_audioChannels->clear(); 1820 if (m_controller->audioInfo()) { 1821 QMap<int, QString> audioStreamsInfo = m_controller->audioStreams(); 1822 if (audioStreamsInfo.size() > 1) { 1823 // Multi stream clip 1824 QMapIterator<int, QString> i(audioStreamsInfo); 1825 QMap<int, QString> activeStreams = m_controller->activeStreams(); 1826 if (activeStreams.size() > 1) { 1827 m_glMonitor->getControllerProxy()->setAudioStream(i18np("%1 audio stream", "%1 audio streams", activeStreams.size())); 1828 // TODO: Mix audio channels 1829 } else if (!activeStreams.isEmpty()) { 1830 m_glMonitor->getControllerProxy()->setAudioStream(activeStreams.first()); 1831 } 1832 QAction *ac; 1833 while (i.hasNext()) { 1834 i.next(); 1835 ac = m_audioChannels->addAction(i.value()); 1836 ac->setData(i.key()); 1837 ac->setCheckable(true); 1838 if (activeStreams.contains(i.key())) { 1839 ac->setChecked(true); 1840 } 1841 } 1842 ac = m_audioChannels->addAction(i18n("Merge all streams")); 1843 ac->setData(INT_MAX); 1844 ac->setCheckable(true); 1845 if (activeStreams.contains(INT_MAX)) { 1846 ac->setChecked(true); 1847 } 1848 m_streamAction->setVisible(true); 1849 } else { 1850 m_streamAction->setVisible(false); 1851 } 1852 } else { 1853 m_streamAction->setVisible(false); 1854 // m_audioChannels->menuAction()->setVisible(false); 1855 } 1856 connect(m_controller.get(), &ProjectClip::audioThumbReady, this, &Monitor::prepareAudioThumb); 1857 if (m_controller->hasLimitedDuration()) { 1858 connect(m_controller.get(), &ProjectClip::boundsChanged, m_glMonitor->getControllerProxy(), &MonitorProxy::updateClipBounds); 1859 connect(m_controller.get(), &ProjectClip::registeredClipChanged, m_controller.get(), &ProjectClip::checkClipBounds); 1860 } 1861 connect(m_controller->getMarkerModel().get(), SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector<int>)), this, SLOT(checkOverlay())); 1862 connect(m_controller->getMarkerModel().get(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(checkOverlay())); 1863 connect(m_controller->getMarkerModel().get(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(checkOverlay())); 1864 1865 if (m_recManager->toolbar()->isVisible()) { 1866 // we are in record mode, don't display clip 1867 return; 1868 } 1869 if (KdenliveSettings::rectimecode()) { 1870 m_timePos->setOffset(m_controller->getRecordTime()); 1871 } 1872 if (m_controller->statusReady()) { 1873 m_timePos->setRange(0, int(m_controller->frameDuration() - 1)); 1874 m_glMonitor->setRulerInfo(int(m_controller->frameDuration() - 1), controller->getFilteredMarkerModel()); 1875 double audioScale = m_controller->getProducerDoubleProperty(QStringLiteral("kdenlive:thumbZoomFactor")); 1876 if (in == out && in == -1) { 1877 // Only apply on bin clip, not sub clips 1878 int lastPosition = m_controller->getProducerIntProperty(QStringLiteral("kdenlive:monitorPosition")); 1879 if (lastPosition > 0 && lastPosition != m_controller->originalProducer()->position()) { 1880 m_controller->originalProducer()->seek(lastPosition); 1881 } 1882 if (audioScale > 0. && audioScale != 1.) { 1883 double audioStart = m_controller->getProducerDoubleProperty(QStringLiteral("kdenlive:thumbZoomStart")); 1884 m_glMonitor->rootObject()->setProperty("zoomFactor", audioScale); 1885 m_glMonitor->rootObject()->setProperty("zoomStart", audioStart); 1886 m_glMonitor->rootObject()->setProperty("showZoomBar", true); 1887 } else { 1888 m_glMonitor->rootObject()->setProperty("zoomFactor", 1); 1889 m_glMonitor->rootObject()->setProperty("zoomStart", 0); 1890 m_glMonitor->rootObject()->setProperty("showZoomBar", false); 1891 } 1892 } else { 1893 m_glMonitor->rootObject()->setProperty("zoomFactor", 1); 1894 m_glMonitor->rootObject()->setProperty("zoomStart", 0); 1895 m_glMonitor->rootObject()->setProperty("showZoomBar", false); 1896 } 1897 pCore->guidesList()->setClipMarkerModel(m_controller); 1898 loadQmlScene(MonitorSceneDefault); 1899 updateMarkers(); 1900 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::addSnap, this, &Monitor::addSnapPoint, Qt::DirectConnection); 1901 connect(m_glMonitor->getControllerProxy(), &MonitorProxy::removeSnap, this, &Monitor::removeSnapPoint, Qt::DirectConnection); 1902 if (out == -1) { 1903 m_glMonitor->getControllerProxy()->setZone(m_controller->zone(), false); 1904 } else { 1905 m_glMonitor->getControllerProxy()->setZone(in, out, false); 1906 } 1907 m_snaps->addPoint(int(m_controller->frameDuration() - 1)); 1908 // Loading new clip / zone, stop if playing 1909 if (m_playAction->isActive()) { 1910 updatePlayAction(false); 1911 } 1912 m_audioMeterWidget->audioChannels = controller->audioInfo() ? controller->audioInfo()->channels() : 0; 1913 m_controller->getMarkerModel()->registerSnapModel(m_snaps); 1914 m_glMonitor->getControllerProxy()->setClipProperties(controller->clipId().toInt(), controller->clipType(), controller->hasAudioAndVideo(), 1915 controller->clipName()); 1916 if (!m_controller->hasVideo() || KdenliveSettings::displayClipMonitorInfo() & 0x10) { 1917 if (m_audioMeterWidget->audioChannels == 0 || !m_controller->hasAudio()) { 1918 qDebug() << "=======\n\nSETTING AUDIO DATA IN MONITOR EMPTY!!!"; 1919 m_glMonitor->getControllerProxy()->setAudioThumb(); 1920 } else { 1921 QList<int> streamIndexes = m_controller->activeStreams().keys(); 1922 qDebug() << "=======\n\nSETTING AUDIO DATA IN MONITOR NOT EMPTY!!!"; 1923 if (streamIndexes.count() == 1 && streamIndexes.at(0) == INT_MAX) { 1924 // Display all streams 1925 streamIndexes = m_controller->audioStreams().keys(); 1926 } 1927 m_glMonitor->getControllerProxy()->setAudioThumb(streamIndexes, m_controller->activeStreamChannels()); 1928 } 1929 } 1930 if (monitorVisible() && !m_monitorManager->projectMonitor()->isPlaying()) { 1931 slotActivateMonitor(); 1932 } 1933 buildBackgroundedProducer(in); 1934 } else { 1935 qDebug() << "*************** CONTROLLER NOT READY"; 1936 } 1937 // hasEffects = controller->hasEffects(); 1938 } else { 1939 m_markerModel = nullptr; 1940 loadQmlScene(MonitorSceneDefault); 1941 m_glMonitor->setProducer(nullptr, isActive(), -1); 1942 m_glMonitor->getControllerProxy()->setAudioThumb(); 1943 m_glMonitor->rootObject()->setProperty("zoomFactor", 1); 1944 m_glMonitor->rootObject()->setProperty("zoomStart", 0); 1945 m_glMonitor->rootObject()->setProperty("showZoomBar", false); 1946 m_audioMeterWidget->audioChannels = 0; 1947 m_glMonitor->getControllerProxy()->setClipProperties(-1, ClipType::Unknown, false, QString()); 1948 pCore->guidesList()->setClipMarkerModel(nullptr); 1949 // m_audioChannels->menuAction()->setVisible(false); 1950 m_streamAction->setVisible(false); 1951 if (monitorVisible()) { 1952 slotActivateMonitor(); 1953 } 1954 } 1955 if (isActive()) { 1956 start(); 1957 } 1958 checkOverlay(); 1959 } 1960 1961 void Monitor::loadZone(int in, int out) 1962 { 1963 m_glMonitor->getControllerProxy()->setZone({in, out}, false); 1964 } 1965 1966 void Monitor::reloadActiveStream() 1967 { 1968 if (m_controller) { 1969 QList<QAction *> acts = m_audioChannels->actions(); 1970 QSignalBlocker bk(m_audioChannels); 1971 QList<int> activeStreams = m_controller->activeStreams().keys(); 1972 QMap<int, QString> streams = m_controller->audioStreams(); 1973 qDebug() << "==== REFRESHING MONITOR STREAMS: " << activeStreams; 1974 if (activeStreams.size() > 1) { 1975 m_glMonitor->getControllerProxy()->setAudioStream(i18np("%1 audio stream", "%1 audio streams", activeStreams.size())); 1976 // TODO: Mix audio channels 1977 } else if (!activeStreams.isEmpty()) { 1978 m_glMonitor->getControllerProxy()->setAudioStream(m_controller->activeStreams().first()); 1979 } else { 1980 m_glMonitor->getControllerProxy()->setAudioStream(QString()); 1981 } 1982 prepareAudioThumb(); 1983 for (auto ac : qAsConst(acts)) { 1984 int val = ac->data().toInt(); 1985 if (streams.contains(val)) { 1986 // Update stream name in case of renaming 1987 ac->setText(streams.value(val)); 1988 } 1989 if (activeStreams.contains(val)) { 1990 ac->setChecked(true); 1991 } else { 1992 ac->setChecked(false); 1993 } 1994 } 1995 } 1996 } 1997 1998 const QString Monitor::activeClipId() 1999 { 2000 if (m_controller) { 2001 return m_controller->clipId(); 2002 } 2003 return QString(); 2004 } 2005 2006 void Monitor::slotPreviewResource(const QString &path, const QString &title) 2007 { 2008 if (isPlaying()) { 2009 stop(); 2010 } 2011 QApplication::processEvents(); 2012 slotOpenClip(nullptr); 2013 m_streamAction->setVisible(false); 2014 // TODO: direct loading of the producer blocks UI, we should use a task to load the producer 2015 m_markerModel = nullptr; 2016 m_glMonitor->setProducer(path); 2017 m_timePos->setRange(0, m_glMonitor->producer()->get_length() - 1); 2018 m_glMonitor->getControllerProxy()->setClipProperties(-1, ClipType::Unknown, false, title); 2019 m_glMonitor->setRulerInfo(m_glMonitor->producer()->get_length() - 1); 2020 loadQmlScene(MonitorSceneDefault); 2021 checkOverlay(); 2022 slotStart(); 2023 switchPlay(true); 2024 } 2025 2026 void Monitor::resetProfile() 2027 { 2028 m_glMonitor->reloadProfile(); 2029 m_glMonitor->rootObject()->setProperty("framesize", QRect(0, 0, m_glMonitor->profileSize().width(), m_glMonitor->profileSize().height())); 2030 // Update drop frame info 2031 m_qmlManager->setProperty(QStringLiteral("dropped"), false); 2032 m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(pCore->getCurrentFps(), 'f', 2)); 2033 } 2034 2035 void Monitor::resetConsumer(bool fullReset) 2036 { 2037 m_glMonitor->resetConsumer(fullReset); 2038 } 2039 2040 void Monitor::updateClipZone(const QPoint zone) 2041 { 2042 if (m_controller == nullptr) { 2043 return; 2044 } 2045 m_controller->setZone(zone); 2046 } 2047 2048 void Monitor::restart() 2049 { 2050 m_glMonitor->restart(); 2051 } 2052 2053 void Monitor::switchMonitorInfo(int code) 2054 { 2055 int currentOverlay; 2056 if (m_id == Kdenlive::ClipMonitor) { 2057 currentOverlay = KdenliveSettings::displayClipMonitorInfo(); 2058 currentOverlay ^= code; 2059 KdenliveSettings::setDisplayClipMonitorInfo(currentOverlay); 2060 } else { 2061 currentOverlay = KdenliveSettings::displayProjectMonitorInfo(); 2062 currentOverlay ^= code; 2063 KdenliveSettings::setDisplayProjectMonitorInfo(currentOverlay); 2064 } 2065 updateQmlDisplay(currentOverlay); 2066 if (code == 0x01) { 2067 // Hide/show ruler 2068 m_glMonitor->switchRuler(currentOverlay & 0x01); 2069 } 2070 } 2071 2072 void Monitor::slotEditMarker() 2073 { 2074 if (m_editMarker) { 2075 m_editMarker->trigger(); 2076 } 2077 } 2078 2079 void Monitor::updateTimecodeFormat() 2080 { 2081 m_glMonitor->rootObject()->setProperty("timecode", m_timePos->displayText()); 2082 } 2083 2084 QPoint Monitor::getZoneInfo() const 2085 { 2086 return m_glMonitor->getControllerProxy()->zone(); 2087 } 2088 2089 void Monitor::enableEffectScene(bool enable) 2090 { 2091 KdenliveSettings::setShowOnMonitorScene(enable); 2092 MonitorSceneType sceneType = enable ? m_lastMonitorSceneType : MonitorSceneDefault; 2093 slotShowEffectScene(sceneType, true); 2094 if (enable) { 2095 Q_EMIT updateScene(); 2096 } 2097 } 2098 2099 void Monitor::slotShowEffectScene(MonitorSceneType sceneType, bool temporary, const QVariant &sceneData) 2100 { 2101 if (m_trimmingbar->isVisible()) { 2102 return; 2103 } 2104 if (sceneType == MonitorSceneNone) { 2105 // We just want to revert to normal scene 2106 if (m_qmlManager->sceneType() == MonitorSceneSplit || m_qmlManager->sceneType() == MonitorSceneDefault) { 2107 // Ok, nothing to do 2108 return; 2109 } 2110 sceneType = MonitorSceneDefault; 2111 } else if (m_qmlManager->sceneType() == MonitorSplitTrack) { 2112 // Don't show another scene type if multitrack mode is active 2113 loadQmlScene(MonitorSplitTrack, sceneData); 2114 return; 2115 } 2116 if (!temporary) { 2117 m_lastMonitorSceneType = sceneType; 2118 } 2119 loadQmlScene(sceneType, sceneData); 2120 } 2121 2122 void Monitor::slotSeekToKeyFrame() 2123 { 2124 if (m_qmlManager->sceneType() == MonitorSceneGeometry) { 2125 // Adjust splitter pos 2126 int kfr = m_glMonitor->rootObject()->property("requestedKeyFrame").toInt(); 2127 Q_EMIT seekToKeyframe(kfr); 2128 } 2129 } 2130 2131 void Monitor::setUpEffectGeometry(const QRect &r, const QVariantList &list, const QVariantList &types) 2132 { 2133 QQuickItem *root = m_glMonitor->rootObject(); 2134 if (!root) { 2135 return; 2136 } 2137 if (!list.isEmpty() || m_qmlManager->sceneType() == MonitorSceneRoto) { 2138 root->setProperty("centerPointsTypes", types); 2139 root->setProperty("centerPoints", list); 2140 } 2141 if (!r.isEmpty()) { 2142 root->setProperty("framesize", r); 2143 } 2144 } 2145 2146 void Monitor::setEffectSceneProperty(const QString &name, const QVariant &value) 2147 { 2148 QQuickItem *root = m_glMonitor->rootObject(); 2149 if (!root) { 2150 return; 2151 } 2152 root->setProperty(name.toUtf8().constData(), value); 2153 } 2154 2155 QRect Monitor::effectRect() const 2156 { 2157 QQuickItem *root = m_glMonitor->rootObject(); 2158 if (!root) { 2159 return {}; 2160 } 2161 return root->property("framesize").toRect(); 2162 } 2163 2164 QVariantList Monitor::effectPolygon() const 2165 { 2166 QQuickItem *root = m_glMonitor->rootObject(); 2167 if (!root) { 2168 return QVariantList(); 2169 } 2170 return root->property("centerPoints").toList(); 2171 } 2172 2173 QVariantList Monitor::effectRoto() const 2174 { 2175 QQuickItem *root = m_glMonitor->rootObject(); 2176 if (!root) { 2177 return QVariantList(); 2178 } 2179 QVariantList points = root->property("centerPoints").toList(); 2180 QVariantList controlPoints = root->property("centerPointsTypes").toList(); 2181 // rotoscoping effect needs a list of 2182 QVariantList mix; 2183 mix.reserve(points.count() * 3); 2184 for (int i = 0; i < points.count(); i++) { 2185 mix << controlPoints.at(2 * i); 2186 mix << points.at(i); 2187 mix << controlPoints.at(2 * i + 1); 2188 } 2189 return mix; 2190 } 2191 2192 void Monitor::setEffectKeyframe(bool enable) 2193 { 2194 QQuickItem *root = m_glMonitor->rootObject(); 2195 if (root) { 2196 root->setProperty("iskeyframe", enable); 2197 } 2198 } 2199 2200 bool Monitor::effectSceneDisplayed(MonitorSceneType effectType) 2201 { 2202 return m_qmlManager->sceneType() == effectType; 2203 } 2204 2205 void Monitor::slotSetVolume(int volume) 2206 { 2207 KdenliveSettings::setVolume(volume); 2208 double renderVolume = m_glMonitor->volume(); 2209 m_glMonitor->setVolume(volume / 100.0); 2210 if (renderVolume > 0 && volume > 0) { 2211 return; 2212 } 2213 /*QIcon icon; 2214 if (volume == 0) { 2215 icon = QIcon::fromTheme(QStringLiteral("audio-volume-muted")); 2216 } else { 2217 icon = QIcon::fromTheme(QStringLiteral("audio-volume-medium")); 2218 }*/ 2219 } 2220 2221 void Monitor::sendFrameForAnalysis(bool analyse) 2222 { 2223 m_glMonitor->sendFrameForAnalysis = analyse; 2224 } 2225 2226 void Monitor::updateAudioForAnalysis() 2227 { 2228 m_glMonitor->updateAudioForAnalysis(); 2229 } 2230 2231 void Monitor::onFrameDisplayed(const SharedFrame &frame) 2232 { 2233 Q_EMIT m_monitorManager->frameDisplayed(frame); 2234 if (m_id == Kdenlive::ProjectMonitor) { 2235 Q_EMIT pCore->updateMixerLevels(frame.get_position()); 2236 } 2237 if (!m_glMonitor->checkFrameNumber(frame.get_position(), m_playAction->isActive())) { 2238 updatePlayAction(false); 2239 } 2240 } 2241 2242 void Monitor::checkDrops() 2243 { 2244 int dropped = m_glMonitor->droppedFrames(); 2245 if (dropped == 0) { 2246 // No dropped frames since last check 2247 m_qmlManager->setProperty(QStringLiteral("dropped"), false); 2248 m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(pCore->getCurrentFps(), 'f', 2)); 2249 } else { 2250 m_glMonitor->resetDrops(); 2251 dropped = int(pCore->getCurrentFps() - dropped); 2252 m_qmlManager->setProperty(QStringLiteral("dropped"), true); 2253 m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(dropped, 'f', 2)); 2254 } 2255 } 2256 2257 void Monitor::reloadProducer(const QString &id) 2258 { 2259 if (!m_controller) { 2260 return; 2261 } 2262 if (m_controller->AbstractProjectItem::clipId() == id) { 2263 slotOpenClip(m_controller); 2264 } 2265 } 2266 2267 QString Monitor::getMarkerThumb(GenTime pos) 2268 { 2269 if (!m_controller) { 2270 return QString(); 2271 } 2272 if (!m_controller->getClipHash().isEmpty()) { 2273 bool ok = false; 2274 QDir dir = pCore->currentDoc()->getCacheDir(CacheThumbs, &ok); 2275 if (ok) { 2276 QString url = dir.absoluteFilePath(m_controller->getClipHash() + QLatin1Char('#') + QString::number(pos.frames(pCore->getCurrentFps())) + 2277 QStringLiteral(".png")); 2278 if (QFile::exists(url)) { 2279 return url; 2280 } 2281 } 2282 } 2283 return QString(); 2284 } 2285 2286 void Monitor::setPalette(const QPalette &p) 2287 { 2288 QWidget::setPalette(p); 2289 QList<QToolButton *> allButtons = this->findChildren<QToolButton *>(); 2290 for (int i = 0; i < allButtons.count(); i++) { 2291 QToolButton *m = allButtons.at(i); 2292 QIcon ic = m->icon(); 2293 if (ic.isNull() || ic.name().isEmpty()) { 2294 continue; 2295 } 2296 QIcon newIcon = QIcon::fromTheme(ic.name()); 2297 m->setIcon(newIcon); 2298 } 2299 QQuickItem *root = m_glMonitor->rootObject(); 2300 if (root) { 2301 QMetaObject::invokeMethod(root, "updatePalette"); 2302 } 2303 m_audioMeterWidget->refreshPixmap(); 2304 } 2305 2306 void Monitor::gpuError() 2307 { 2308 qCWarning(KDENLIVE_LOG) << " + + + + Error initializing Movit GLSL manager"; 2309 warningMessage(i18n("Cannot initialize Movit's GLSL manager, please disable Movit"), -1); 2310 } 2311 2312 void Monitor::warningMessage(const QString &text, int timeout, const QList<QAction *> &actions) 2313 { 2314 m_infoMessage->setMessageType(KMessageWidget::Warning); 2315 m_infoMessage->setText(text); 2316 for (QAction *action : actions) { 2317 m_infoMessage->addAction(action); 2318 } 2319 m_infoMessage->setCloseButtonVisible(true); 2320 m_infoMessage->animatedShow(); 2321 if (timeout > 0) { 2322 QTimer::singleShot(timeout, m_infoMessage, &KMessageWidget::animatedHide); 2323 } 2324 } 2325 2326 void Monitor::activateSplit() 2327 { 2328 loadQmlScene(MonitorSceneSplit); 2329 if (isActive()) { 2330 m_glMonitor->requestRefresh(); 2331 } else if (slotActivateMonitor()) { 2332 start(); 2333 } 2334 } 2335 2336 void Monitor::slotSwitchCompare(bool enable) 2337 { 2338 if (m_id == Kdenlive::ProjectMonitor) { 2339 if (enable) { 2340 if (m_qmlManager->sceneType() == MonitorSceneSplit) { 2341 // Split scene is already active 2342 return; 2343 } 2344 m_splitEffect.reset(new Mlt::Filter(pCore->getProjectProfile(), "frei0r.alphagrad")); 2345 if ((m_splitEffect != nullptr) && m_splitEffect->is_valid()) { 2346 m_splitEffect->set("0", 0.5); // 0 is the Clip left parameter 2347 m_splitEffect->set("1", 0); // 1 is gradient width 2348 m_splitEffect->set("2", -0.747); // 2 is tilt 2349 } else { 2350 // frei0r.scal0tilt is not available 2351 warningMessage(i18n("The alphagrad filter is required for that feature, please install frei0r and restart Kdenlive")); 2352 return; 2353 } 2354 Q_EMIT createSplitOverlay(m_splitEffect); 2355 return; 2356 } 2357 // Delete temp track 2358 Q_EMIT removeSplitOverlay(); 2359 m_splitEffect.reset(); 2360 loadQmlScene(MonitorSceneDefault); 2361 if (isActive()) { 2362 m_glMonitor->requestRefresh(); 2363 } else if (slotActivateMonitor()) { 2364 start(); 2365 } 2366 return; 2367 } 2368 if (m_controller == nullptr || !m_controller->hasEffects()) { 2369 // disable split effect 2370 if (m_controller) { 2371 pCore->displayMessage(i18n("Clip has no effects"), InformationMessage); 2372 } else { 2373 pCore->displayMessage(i18n("Select a clip in project bin to compare effect"), InformationMessage); 2374 } 2375 return; 2376 } 2377 if (enable) { 2378 if (m_qmlManager->sceneType() == MonitorSceneSplit) { 2379 // Split scene is already active 2380 qDebug() << " . . . .. ALREADY ACTIVE"; 2381 return; 2382 } 2383 buildSplitEffect(m_controller->masterProducer()); 2384 } else if (m_splitEffect) { 2385 // TODO 2386 m_glMonitor->setProducer(m_controller->originalProducer(), isActive(), position()); 2387 m_splitEffect.reset(); 2388 m_splitProducer.reset(); 2389 loadQmlScene(MonitorSceneDefault); 2390 } 2391 slotActivateMonitor(); 2392 } 2393 2394 void Monitor::resetScene() 2395 { 2396 loadQmlScene(MonitorSceneDefault); 2397 } 2398 2399 void Monitor::buildSplitEffect(Mlt::Producer *original) 2400 { 2401 m_splitEffect.reset(new Mlt::Filter(pCore->getProjectProfile(), "frei0r.alphagrad")); 2402 if ((m_splitEffect != nullptr) && m_splitEffect->is_valid()) { 2403 m_splitEffect->set("0", 0.5); // 0 is the Clip left parameter 2404 m_splitEffect->set("1", 0); // 1 is gradient width 2405 m_splitEffect->set("2", -0.747); // 2 is tilt 2406 } else { 2407 // frei0r.scal0tilt is not available 2408 pCore->displayMessage(i18n("The alphagrad filter is required for that feature, please install frei0r and restart Kdenlive"), ErrorMessage); 2409 return; 2410 } 2411 QString splitTransition = TransitionsRepository::get()->getCompositingTransition(); 2412 Mlt::Transition t(pCore->getProjectProfile(), splitTransition.toUtf8().constData()); 2413 if (!t.is_valid()) { 2414 m_splitEffect.reset(); 2415 pCore->displayMessage(i18n("The cairoblend transition is required for that feature, please install frei0r and restart Kdenlive"), ErrorMessage); 2416 return; 2417 } 2418 Mlt::Tractor trac(pCore->getProjectProfile()); 2419 std::shared_ptr<Mlt::Producer> clone = ProjectClip::cloneProducer(std::make_shared<Mlt::Producer>(original)); 2420 // Delete all effects 2421 int ct = 0; 2422 Mlt::Filter *filter = clone->filter(ct); 2423 while (filter != nullptr) { 2424 QString ix = QString::fromLatin1(filter->get("kdenlive_id")); 2425 if (!ix.isEmpty()) { 2426 if (clone->detach(*filter) == 0) { 2427 } else { 2428 ct++; 2429 } 2430 } else { 2431 ct++; 2432 } 2433 delete filter; 2434 filter = clone->filter(ct); 2435 } 2436 trac.set_track(*original, 0); 2437 trac.set_track(*clone.get(), 1); 2438 clone.get()->attach(*m_splitEffect.get()); 2439 t.set("always_active", 1); 2440 trac.plant_transition(t, 0, 1); 2441 delete original; 2442 m_splitProducer = std::make_shared<Mlt::Producer>(trac.get_producer()); 2443 m_glMonitor->setProducer(m_splitProducer, isActive(), position()); 2444 m_glMonitor->setRulerInfo(int(m_controller->frameDuration()), m_controller->getFilteredMarkerModel()); 2445 loadQmlScene(MonitorSceneSplit); 2446 } 2447 2448 QSize Monitor::profileSize() const 2449 { 2450 return m_glMonitor->profileSize(); 2451 } 2452 2453 void Monitor::loadQmlScene(MonitorSceneType type, const QVariant &sceneData) 2454 { 2455 if (type == m_qmlManager->sceneType() && sceneData.isNull()) { 2456 return; 2457 } 2458 bool sceneWithEdit = type == MonitorSceneGeometry || type == MonitorSceneCorners || type == MonitorSceneRoto; 2459 if (!m_monitorManager->getAction(QStringLiteral("monitor_editmode"))->isChecked() && sceneWithEdit) { 2460 // User doesn't want effect scenes 2461 pCore->displayMessage(i18n("Enable edit mode in monitor to edit effect"), InformationMessage, 500); 2462 type = MonitorSceneDefault; 2463 } 2464 m_qmlManager->setScene(m_id, type, pCore->getCurrentFrameSize(), pCore->getCurrentDar(), m_glMonitor->displayRect(), double(m_glMonitor->zoom()), 2465 m_timePos->maximum()); 2466 if (m_glMonitor->zoom() != 1.) { 2467 m_glMonitor->setZoom(m_glMonitor->zoom(), true); 2468 } 2469 QQuickItem *root = m_glMonitor->rootObject(); 2470 switch (type) { 2471 case MonitorSceneSplit: 2472 QObject::connect(root, SIGNAL(qmlMoveSplit()), this, SLOT(slotAdjustEffectCompare()), Qt::UniqueConnection); 2473 break; 2474 case MonitorSceneTrimming: 2475 case MonitorSceneGeometry: 2476 case MonitorSceneCorners: 2477 case MonitorSceneRoto: 2478 break; 2479 case MonitorSceneDefault: 2480 QObject::connect(root, SIGNAL(editCurrentMarker()), this, SLOT(slotEditInlineMarker()), Qt::UniqueConnection); 2481 m_qmlManager->setProperty(QStringLiteral("timecode"), m_timePos->displayText()); 2482 if (m_id == Kdenlive::ClipMonitor) { 2483 QObject::connect(root, SIGNAL(endDrag()), pCore->bin(), SIGNAL(processDragEnd()), Qt::UniqueConnection); 2484 updateQmlDisplay(KdenliveSettings::displayClipMonitorInfo()); 2485 } else if (m_id == Kdenlive::ProjectMonitor) { 2486 updateQmlDisplay(KdenliveSettings::displayProjectMonitorInfo()); 2487 QObject::connect(root, SIGNAL(startRecording()), pCore.get(), SLOT(startRecording()), Qt::UniqueConnection); 2488 } 2489 break; 2490 case MonitorSplitTrack: 2491 m_qmlManager->setProperty(QStringLiteral("tracks"), sceneData); 2492 break; 2493 default: 2494 break; 2495 } 2496 m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(pCore->getCurrentFps(), 'f', 2)); 2497 } 2498 2499 void Monitor::setQmlProperty(const QString &name, const QVariant &value) 2500 { 2501 m_qmlManager->setProperty(name, value); 2502 } 2503 2504 void Monitor::slotAdjustEffectCompare() 2505 { 2506 double percent = 0.5; 2507 if (m_qmlManager->sceneType() == MonitorSceneSplit) { 2508 // Adjust splitter pos 2509 QQuickItem *root = m_glMonitor->rootObject(); 2510 percent = root->property("percentage").toDouble(); 2511 // Store real frame percentage for resize events 2512 root->setProperty("realpercent", percent); 2513 } 2514 if (m_splitEffect) { 2515 m_splitEffect->set("0", 0.5 - (percent - 0.5) * .666); 2516 } 2517 m_glMonitor->refresh(); 2518 } 2519 2520 void Monitor::slotSwitchRec(bool enable) 2521 { 2522 if (!m_recManager) { 2523 return; 2524 } 2525 if (enable) { 2526 m_toolbar->setVisible(false); 2527 m_recManager->toolbar()->setVisible(true); 2528 } else if (m_recManager->toolbar()->isVisible()) { 2529 m_recManager->stop(); 2530 m_toolbar->setVisible(true); 2531 Q_EMIT refreshCurrentClip(); 2532 } 2533 } 2534 2535 void Monitor::slotSwitchTrimming(bool enable) 2536 { 2537 if (!m_trimmingbar) { 2538 return; 2539 } 2540 if (enable) { 2541 loadQmlScene(MonitorSceneTrimming); 2542 m_toolbar->setVisible(false); 2543 m_trimmingbar->setVisible(true); 2544 if (pCore->activeTool() == ToolType::RippleTool) { 2545 m_oneLess->setVisible(false); 2546 m_oneMore->setVisible(false); 2547 m_fiveLess->setVisible(false); 2548 m_fiveMore->setVisible(false); 2549 } else { 2550 m_oneLess->setVisible(true); 2551 m_oneMore->setVisible(true); 2552 m_fiveLess->setVisible(true); 2553 m_fiveMore->setVisible(true); 2554 } 2555 m_glMonitor->switchRuler(false); 2556 } else if (m_trimmingbar->isVisible()) { 2557 loadQmlScene(MonitorSceneDefault); 2558 m_trimmingbar->setVisible(false); 2559 m_toolbar->setVisible(true); 2560 m_glMonitor->switchRuler(KdenliveSettings::displayClipMonitorInfo() & 0x01); 2561 } 2562 } 2563 2564 void Monitor::doKeyPressEvent(QKeyEvent *ev) 2565 { 2566 keyPressEvent(ev); 2567 } 2568 2569 void Monitor::slotEditInlineMarker() 2570 { 2571 QQuickItem *root = m_glMonitor->rootObject(); 2572 if (root) { 2573 std::shared_ptr<MarkerListModel> model; 2574 if (m_controller) { 2575 // We are editing a clip marker 2576 model = m_controller->getMarkerModel(); 2577 } else { 2578 model = pCore->currentDoc()->getGuideModel(pCore->currentTimelineId()); 2579 } 2580 QString newComment = root->property("markerText").toString(); 2581 bool found = false; 2582 CommentedTime oldMarker = model->getMarker(m_timePos->getValue(), &found); 2583 if (!found || newComment == oldMarker.comment()) { 2584 // No change 2585 return; 2586 } 2587 oldMarker.setComment(newComment); 2588 model->addMarker(oldMarker.time(), oldMarker.comment(), oldMarker.markerType()); 2589 } 2590 } 2591 2592 void Monitor::prepareAudioThumb() 2593 { 2594 if (m_controller) { 2595 m_glMonitor->getControllerProxy()->setAudioThumb(); 2596 if (!m_controller->audioStreams().isEmpty() && m_controller->hasAudio()) { 2597 QList<int> streamIndexes = m_controller->activeStreams().keys(); 2598 if (streamIndexes.count() == 1 && streamIndexes.at(0) == INT_MAX) { 2599 // Display all streams 2600 streamIndexes = m_controller->audioStreams().keys(); 2601 } 2602 m_glMonitor->getControllerProxy()->setAudioThumb(streamIndexes, m_controller->activeStreamChannels()); 2603 } 2604 } 2605 } 2606 2607 void Monitor::slotSwitchAudioMonitor() 2608 { 2609 if (!m_audioMeterWidget->isValid) { 2610 KdenliveSettings::setMonitoraudio(0x01); 2611 m_audioMeterWidget->setVisibility(false); 2612 return; 2613 } 2614 int currentOverlay = KdenliveSettings::monitoraudio(); 2615 currentOverlay ^= m_id; 2616 KdenliveSettings::setMonitoraudio(currentOverlay); 2617 if ((KdenliveSettings::monitoraudio() & m_id) != 0) { 2618 // We want to enable this audio monitor, so make monitor active 2619 slotActivateMonitor(); 2620 } 2621 displayAudioMonitor(isActive()); 2622 } 2623 2624 void Monitor::updateGuidesList() 2625 { 2626 if (m_id == Kdenlive::ProjectMonitor) { 2627 if (pCore->currentDoc()) { 2628 const QUuid uuid = pCore->currentDoc()->activeUuid; 2629 if (!uuid.isNull()) { 2630 pCore->guidesList()->setModel(pCore->currentDoc()->getGuideModel(uuid), pCore->currentDoc()->getFilteredGuideModel(uuid)); 2631 } 2632 } 2633 } else if (m_id == Kdenlive::ClipMonitor) { 2634 pCore->guidesList()->setClipMarkerModel(m_controller); 2635 } 2636 } 2637 2638 void Monitor::displayAudioMonitor(bool isActive) 2639 { 2640 bool enable = isActive && ((KdenliveSettings::monitoraudio() & m_id) != 0 || (m_id == Kdenlive::ProjectMonitor && pCore->audioMixerVisible)); 2641 if (enable) { 2642 connect(m_monitorManager, &MonitorManager::frameDisplayed, m_audioMeterWidget, &ScopeWidget::onNewFrame, Qt::UniqueConnection); 2643 } else { 2644 disconnect(m_monitorManager, &MonitorManager::frameDisplayed, m_audioMeterWidget, &ScopeWidget::onNewFrame); 2645 } 2646 m_audioMeterWidget->setVisibility((KdenliveSettings::monitoraudio() & m_id) != 0); 2647 if (isActive && m_glWidget->isFullScreen()) { 2648 // If both monitors are fullscreen, this is necessary to do the switch 2649 m_glWidget->showFullScreen(); 2650 pCore->window()->activateWindow(); 2651 pCore->window()->setFocus(); 2652 } 2653 } 2654 2655 void Monitor::updateQmlDisplay(int currentOverlay) 2656 { 2657 m_glMonitor->rootObject()->setVisible((currentOverlay & 0x01) != 0); 2658 m_glMonitor->rootObject()->setProperty("showMarkers", currentOverlay & 0x04); 2659 bool showDropped = currentOverlay & 0x20; 2660 m_glMonitor->rootObject()->setProperty("showFps", showDropped); 2661 m_glMonitor->rootObject()->setProperty("showTimecode", currentOverlay & 0x02); 2662 if (m_id == Kdenlive::ClipMonitor) { 2663 m_glMonitor->rootObject()->setProperty("showAudiothumb", currentOverlay & 0x10); 2664 m_glMonitor->rootObject()->setProperty("showClipJobs", currentOverlay & 0x40); 2665 } 2666 if (showDropped) { 2667 if (!m_droppedTimer.isActive() && m_playAction->isActive()) { 2668 m_glMonitor->resetDrops(); 2669 m_droppedTimer.start(); 2670 } 2671 } else { 2672 m_droppedTimer.stop(); 2673 } 2674 } 2675 2676 void Monitor::clearDisplay() 2677 { 2678 m_glMonitor->clear(); 2679 } 2680 2681 void Monitor::panView(QPoint diff) 2682 { 2683 // Only pan if scrollbars are visible 2684 if (m_horizontalScroll->isVisible()) { 2685 m_horizontalScroll->setValue(m_horizontalScroll->value() + diff.x()); 2686 } 2687 if (m_verticalScroll->isVisible()) { 2688 m_verticalScroll->setValue(m_verticalScroll->value() + diff.y()); 2689 } 2690 } 2691 2692 void Monitor::processSeek(int pos, bool noAudioScrub) 2693 { 2694 if (!slotActivateMonitor()) { 2695 return; 2696 } 2697 if (KdenliveSettings::pauseonseek()) { 2698 if (m_playAction->isActive()) { 2699 pause(); 2700 } else { 2701 m_glMonitor->setVolume(KdenliveSettings::volume() / 100.); 2702 } 2703 } 2704 m_glMonitor->requestSeek(pos, noAudioScrub); 2705 Q_EMIT m_monitorManager->cleanMixer(); 2706 } 2707 2708 void Monitor::requestSeek(int pos) 2709 { 2710 m_glMonitor->getControllerProxy()->setPosition(pos); 2711 } 2712 2713 void Monitor::requestSeekIfVisible(int pos) 2714 { 2715 if (monitorVisible()) { 2716 requestSeek(pos); 2717 } 2718 } 2719 2720 void Monitor::setProducer(std::shared_ptr<Mlt::Producer> producer, int pos) 2721 { 2722 if (locked) { 2723 return; 2724 } 2725 m_audioMeterWidget->audioChannels = pCore->audioChannels(); 2726 if (producer) { 2727 m_markerModel = pCore->currentDoc()->getGuideModel(pCore->currentTimelineId()); 2728 } else { 2729 m_markerModel.reset(); 2730 } 2731 m_glMonitor->setProducer(std::move(producer), isActive(), pos); 2732 } 2733 2734 void Monitor::reconfigure() 2735 { 2736 m_glMonitor->reconfigure(); 2737 } 2738 2739 void Monitor::slotSeekPosition(int pos) 2740 { 2741 Q_EMIT seekPosition(pos); 2742 m_timePos->setValue(pos); 2743 checkOverlay(); 2744 } 2745 2746 void Monitor::slotStart() 2747 { 2748 if (!slotActivateMonitor()) { 2749 return; 2750 } 2751 m_glMonitor->switchPlay(false); 2752 m_glMonitor->getControllerProxy()->setPosition(0); 2753 } 2754 2755 void Monitor::slotTrimmingPos(int pos, int offset, int frames1, int frames2) 2756 { 2757 if (m_glMonitor->producer() != pCore->window()->getCurrentTimeline()->model()->producer().get()) { 2758 processSeek(pos); 2759 } 2760 QString tc(pCore->timecode().getDisplayTimecodeFromFrames(offset, KdenliveSettings::frametimecode())); 2761 m_trimmingOffset->setText(tc); 2762 m_glMonitor->getControllerProxy()->setTrimmingTC1(frames1); 2763 m_glMonitor->getControllerProxy()->setTrimmingTC2(frames2); 2764 } 2765 2766 void Monitor::slotTrimmingPos(int offset) 2767 { 2768 offset = pCore->window()->getCurrentTimeline()->controller()->trimmingBoundOffset(offset); 2769 if (m_glMonitor->producer() != pCore->window()->getCurrentTimeline()->model()->producer().get()) { 2770 processSeek(m_glMonitor->producer()->position() + offset); 2771 } 2772 QString tc(pCore->timecode().getDisplayTimecodeFromFrames(offset, KdenliveSettings::frametimecode())); 2773 m_trimmingOffset->setText(tc); 2774 2775 m_glMonitor->getControllerProxy()->setTrimmingTC1(offset, true); 2776 m_glMonitor->getControllerProxy()->setTrimmingTC2(offset, true); 2777 } 2778 2779 void Monitor::slotEnd() 2780 { 2781 if (!slotActivateMonitor()) { 2782 return; 2783 } 2784 m_glMonitor->switchPlay(false); 2785 if (m_id == Kdenlive::ClipMonitor) { 2786 m_glMonitor->getControllerProxy()->setPosition(m_glMonitor->duration() - 1); 2787 } else { 2788 m_glMonitor->getControllerProxy()->setPosition(pCore->projectDuration() - 1); 2789 } 2790 } 2791 2792 void Monitor::addSnapPoint(int pos) 2793 { 2794 m_snaps->addPoint(pos); 2795 } 2796 2797 void Monitor::removeSnapPoint(int pos) 2798 { 2799 m_snaps->removePoint(pos); 2800 } 2801 2802 void Monitor::slotSetScreen(int screenIndex) 2803 { 2804 Q_EMIT screenChanged(screenIndex); 2805 } 2806 2807 void Monitor::slotZoomIn() 2808 { 2809 m_glMonitor->slotZoom(true); 2810 } 2811 2812 void Monitor::slotZoomOut() 2813 { 2814 m_glMonitor->slotZoom(false); 2815 } 2816 2817 void Monitor::setConsumerProperty(const QString &name, const QString &value) 2818 { 2819 m_glMonitor->setConsumerProperty(name, value); 2820 } 2821 2822 void Monitor::purgeCache() 2823 { 2824 m_glMonitor->purgeCache(); 2825 } 2826 2827 void Monitor::updateBgColor() 2828 { 2829 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 2830 m_glMonitor->m_bgColor = KdenliveSettings::window_background(); 2831 #else 2832 m_glMonitor->setClearColor(KdenliveSettings::window_background()); 2833 #endif 2834 } 2835 2836 MonitorProxy *Monitor::getControllerProxy() 2837 { 2838 return m_glMonitor->getControllerProxy(); 2839 } 2840 2841 void Monitor::updateMultiTrackView(int tid) 2842 { 2843 QQuickItem *root = m_glMonitor->rootObject(); 2844 if (root) { 2845 root->setProperty("activeTrack", tid); 2846 } 2847 } 2848 2849 void Monitor::slotSwitchRecTimecode(bool enable) 2850 { 2851 qDebug() << "=== SLOT SWITCH REC: " << enable; 2852 KdenliveSettings::setRectimecode(enable); 2853 if (!enable) { 2854 m_timePos->setOffset(0); 2855 return; 2856 } 2857 if (m_controller) { 2858 qDebug() << "=== GOT TIMECODE OFFSET: " << m_controller->getRecordTime(); 2859 m_timePos->setOffset(m_controller->getRecordTime()); 2860 } 2861 } 2862 2863 void Monitor::focusTimecode() 2864 { 2865 m_timePos->setFocus(); 2866 m_timePos->selectAll(); 2867 } 2868 2869 void Monitor::startCountDown() 2870 { 2871 QQuickItem *root = m_glMonitor->rootObject(); 2872 if (root) { 2873 QMetaObject::invokeMethod(root, "startCountdown"); 2874 } 2875 } 2876 2877 void Monitor::stopCountDown() 2878 { 2879 QQuickItem *root = m_glMonitor->rootObject(); 2880 if (root) { 2881 QMetaObject::invokeMethod(root, "stopCountdown"); 2882 } 2883 } 2884 2885 void Monitor::extractFrame(const QString &path) 2886 { 2887 QStringList pathInfo = {QString(), path, QString()}; 2888 m_glMonitor->getControllerProxy()->extractFrameToFile(m_glMonitor->getCurrentPos(), pathInfo, false, true); 2889 } 2890 2891 const QStringList Monitor::getGPUInfo() 2892 { 2893 return m_glMonitor->getGPUInfo(); 2894 }