File indexing completed on 2024-04-28 08:43:47
0001 /* 0002 SPDX-FileCopyrightText: 2021 Jean-Baptiste Mardelle <jb@kdenlive.org> 0003 0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "timeremap.h" 0008 0009 #include "bin/projectclip.h" 0010 #include "core.h" 0011 #include "doc/kdenlivedoc.h" 0012 #include "doc/kthumb.h" 0013 #include "kdenlivesettings.h" 0014 #include "macros.hpp" 0015 #include "mainwindow.h" 0016 #include "monitor/monitor.h" 0017 #include "profiles/profilemodel.hpp" 0018 #include "project/projectmanager.h" 0019 #include "timeline2/model/clipmodel.hpp" 0020 #include "timeline2/model/groupsmodel.hpp" 0021 #include "timeline2/view/timelinecontroller.h" 0022 #include "timeline2/view/timelinewidget.h" 0023 #include "widgets/timecodedisplay.h" 0024 0025 #include "kdenlive_debug.h" 0026 #include <QFontDatabase> 0027 #include <QStylePainter> 0028 #include <QWheelEvent> 0029 #include <QtMath> 0030 0031 #include "klocalizedstring.h" 0032 #include <KColorScheme> 0033 0034 RemapView::RemapView(QWidget *parent) 0035 : QWidget(parent) 0036 , m_inFrame(0) 0037 , m_duration(1) 0038 , m_position(0) 0039 , m_bottomPosition(0) 0040 , m_scale(1.) 0041 , m_zoomFactor(1) 0042 , m_zoomStart(0) 0043 , m_zoomHandle(0, 1) 0044 , m_clip(nullptr) 0045 , m_service(nullptr) 0046 , m_moveKeyframeMode(NoMove) 0047 , m_clickPoint(-1) 0048 , m_moveNext(true) 0049 { 0050 setMouseTracking(true); 0051 setMinimumSize(QSize(150, 80)); 0052 setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 0053 int size = QFontInfo(font()).pixelSize() * 3; 0054 setFixedHeight(size * 4); 0055 // Reference height of the rulers 0056 m_lineHeight = int(size / 2.); 0057 // Height of the zoom bar 0058 m_zoomHeight = m_lineHeight * 0.5; 0059 // Center of the view 0060 m_centerPos = (size * 4 - m_zoomHeight - 2) / 2 - 1; 0061 m_offset = qCeil(m_lineHeight / 4); 0062 // Bottom of the view (just above zoombar) 0063 m_bottomView = height() - m_zoomHeight - 2; 0064 setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); 0065 int maxWidth = width() - (2 * m_offset); 0066 m_scale = 1.; 0067 m_zoomStart = m_zoomHandle.x() * maxWidth; 0068 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 0069 timer.setInterval(500); 0070 timer.setSingleShot(true); 0071 connect(&timer, &QTimer::timeout, this, &RemapView::reloadProducer); 0072 } 0073 0074 bool RemapView::isInRange() const 0075 { 0076 return m_bottomPosition != -1; 0077 } 0078 0079 void RemapView::updateInPos(int pos) 0080 { 0081 if (m_currentKeyframe.second > -1) { 0082 m_keyframesOrigin = m_keyframes; 0083 if (m_moveNext) { 0084 int offset = pos - m_currentKeyframe.second; 0085 QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first); 0086 while (it != m_keyframes.end()) { 0087 m_keyframes.insert(it.key(), it.value() + offset); 0088 it++; 0089 } 0090 m_currentKeyframe.second = pos; 0091 } else { 0092 m_currentKeyframe.second = pos; 0093 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second); 0094 } 0095 slotSetPosition(pos); 0096 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 0097 Q_EMIT updateSpeeds(speeds); 0098 Q_EMIT updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin); 0099 update(); 0100 } 0101 } 0102 0103 void RemapView::updateOutPos(int pos) 0104 { 0105 if (m_currentKeyframe.first > -1) { 0106 if (m_keyframes.contains(pos)) { 0107 // Cannot move kfr over an existing one 0108 qDebug() << "==== KEYFRAME ALREADY EXISTS AT: " << pos; 0109 return; 0110 } 0111 m_keyframesOrigin = m_keyframes; 0112 QMap<int, int> updated; 0113 int offset = pos - m_currentKeyframe.first; 0114 if (m_moveNext) { 0115 QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first); 0116 while (it != m_keyframes.end()) { 0117 updated.insert(it.key(), it.value()); 0118 it++; 0119 } 0120 m_currentKeyframe.first = pos; 0121 } else { 0122 m_keyframes.remove(m_currentKeyframe.first); 0123 m_currentKeyframe.first = pos; 0124 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second); 0125 } 0126 m_selectedKeyframes = {m_currentKeyframe}; 0127 QMapIterator<int, int> i(updated); 0128 while (i.hasNext()) { 0129 i.next(); 0130 m_keyframes.remove(i.key()); 0131 } 0132 i.toFront(); 0133 while (i.hasNext()) { 0134 i.next(); 0135 m_keyframes.insert(i.key() + offset, i.value()); 0136 } 0137 m_bottomPosition = pos - m_inFrame; 0138 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 0139 Q_EMIT updateSpeeds(speeds); 0140 Q_EMIT updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin); 0141 update(); 0142 } 0143 } 0144 0145 int RemapView::remapDuration() const 0146 { 0147 int maxDuration = 0; 0148 if (m_keyframes.isEmpty()) { 0149 return 0; 0150 } 0151 QMapIterator<int, int> i(m_keyframes); 0152 while (i.hasNext()) { 0153 i.next(); 0154 if (i.key() > maxDuration) { 0155 maxDuration = i.key(); 0156 } 0157 } 0158 return maxDuration - m_inFrame + 1; 0159 } 0160 0161 int RemapView::remapMax() const 0162 { 0163 int maxDuration = 0; 0164 if (m_keyframes.isEmpty()) { 0165 return 0; 0166 } 0167 QMapIterator<int, int> i(m_keyframes); 0168 while (i.hasNext()) { 0169 i.next(); 0170 if (i.value() > maxDuration) { 0171 maxDuration = i.value(); 0172 } 0173 if (i.key() > maxDuration) { 0174 maxDuration = i.key(); 0175 } 0176 } 0177 return maxDuration - m_inFrame + 1; 0178 } 0179 0180 bool RemapView::movingKeyframe() const 0181 { 0182 return m_moveKeyframeMode == BottomMove; 0183 } 0184 0185 void RemapView::setBinClipDuration(std::shared_ptr<ProjectClip> clip, int duration) 0186 { 0187 m_clip = clip; 0188 m_service = clip->originalProducer(); 0189 m_duration = duration; 0190 int maxWidth = width() - (2 * m_offset); 0191 m_scale = maxWidth / double(qMax(1, remapMax())); 0192 m_currentKeyframe = m_currentKeyframeOriginal = {-1, -1}; 0193 } 0194 0195 void RemapView::setDuration(std::shared_ptr<Mlt::Producer> service, int duration, int sourceDuration) 0196 { 0197 m_clip.reset(); 0198 m_sourceDuration = sourceDuration; 0199 if (duration < 0) { 0200 // reset 0201 m_service.reset(); 0202 m_inFrame = 0; 0203 m_duration = -1; 0204 m_selectedKeyframes.clear(); 0205 m_keyframes.clear(); 0206 return; 0207 } 0208 if (service) { 0209 m_service = service; 0210 m_inFrame = 0; 0211 m_duration = -1; 0212 } 0213 bool keyframeAdded = false; 0214 if (m_duration > 0 && m_service && !m_keyframes.isEmpty()) { 0215 m_keyframesOrigin = m_keyframes; 0216 if (duration > m_duration) { 0217 // The clip was resized, ensure we have a keyframe at the end of the clip will freeze at last keyframe 0218 QMap<int, int>::iterator it = m_keyframes.end(); 0219 it--; 0220 int lastKeyframePos = it.key(); 0221 int lastKeyframeValue = it.value(); 0222 int lastPos = m_inFrame + duration - 1; 0223 if (lastPos > lastKeyframePos) { 0224 keyframeAdded = true; 0225 std::pair<double, double> speeds = getSpeed({lastKeyframePos, lastKeyframeValue}); 0226 // Move last keyframe 0227 it--; 0228 int updatedVal = it.value() + ((lastPos - it.key()) * speeds.first); 0229 m_keyframes.remove(lastKeyframePos); 0230 m_keyframes.insert(lastPos, updatedVal); 0231 } 0232 } else if (duration < m_duration) { 0233 keyframeAdded = true; 0234 // Remove all Keyframes after duration 0235 int lastPos = m_inFrame + duration - 1; 0236 QList<int> toDelete; 0237 QMapIterator<int, int> i(m_keyframes); 0238 while (i.hasNext()) { 0239 i.next(); 0240 if (i.key() > duration + m_inFrame) { 0241 toDelete << i.key(); 0242 } 0243 } 0244 if (!toDelete.isEmpty()) { 0245 // Move last keyframe to end pos 0246 int posToMove = toDelete.takeFirst(); 0247 if (!m_keyframes.contains(lastPos)) { 0248 int lastKeyframeValue = m_keyframes.value(posToMove); 0249 std::pair<double, double> speeds = getSpeed({posToMove, lastKeyframeValue}); 0250 m_keyframes.remove(posToMove); 0251 while (!toDelete.isEmpty()) { 0252 m_keyframes.remove(toDelete.takeFirst()); 0253 } 0254 int updatedVal = m_keyframes.value(m_keyframes.lastKey()) + ((lastPos - m_keyframes.lastKey()) / speeds.first); 0255 m_keyframes.insert(lastPos, updatedVal); 0256 } 0257 } 0258 } 0259 if (m_keyframes != m_keyframesOrigin) { 0260 Q_EMIT updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin); 0261 } 0262 } 0263 if (service == nullptr) { 0264 // We are updating an existing remap effect due to duration change 0265 m_duration = qMax(duration, remapMax()); 0266 } else { 0267 m_duration = duration; 0268 m_inFrame = m_service->get_in(); 0269 m_originalRange = {m_inFrame, duration + m_inFrame}; 0270 } 0271 int maxWidth = width() - (2 * m_offset); 0272 m_scale = maxWidth / double(qMax(1, remapMax() - 1)); 0273 m_zoomStart = m_zoomHandle.x() * maxWidth; 0274 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 0275 if (!m_keyframes.contains(m_currentKeyframe.first)) { 0276 m_currentKeyframe = m_currentKeyframeOriginal = {-1, -1}; 0277 update(); 0278 } 0279 if (keyframeAdded) { 0280 Q_EMIT updateKeyframes(false); 0281 } 0282 } 0283 0284 void RemapView::loadKeyframes(const QString &mapData) 0285 { 0286 m_keyframes.clear(); 0287 if (mapData.isEmpty()) { 0288 if (m_inFrame > 0) { 0289 // Necessary otherwise clip will freeze before first keyframe 0290 m_keyframes.insert(0, 0); 0291 } 0292 m_currentKeyframe = {m_inFrame, m_inFrame}; 0293 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second); 0294 m_keyframes.insert(m_inFrame + m_duration - 1, m_inFrame + m_duration - 1); 0295 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 0296 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 0297 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 0298 Q_EMIT atKeyframe(true, true); 0299 Q_EMIT updateKeyframes(false); 0300 } else { 0301 QStringList str = mapData.split(QLatin1Char(';')); 0302 for (auto &s : str) { 0303 int pos = m_service->time_to_frames(s.section(QLatin1Char('='), 0, 0).toUtf8().constData()); 0304 int val = GenTime(s.section(QLatin1Char('='), 1).toDouble()).frames(pCore->getCurrentFps()); 0305 // HACK: we always set last keyframe 1 frame after in MLT to ensure we have a correct last frame 0306 if (s == str.constLast()) { 0307 pos--; 0308 } 0309 m_keyframes.insert(pos, val); 0310 m_duration = qMax(m_duration, pos - m_inFrame); 0311 m_duration = qMax(m_duration, val - m_inFrame); 0312 } 0313 bool isKfr = m_keyframes.contains(m_bottomPosition + m_inFrame); 0314 if (isKfr) { 0315 bool isLast = m_bottomPosition + m_inFrame == m_keyframes.firstKey() || m_bottomPosition + m_inFrame == m_keyframes.lastKey(); 0316 Q_EMIT atKeyframe(isKfr, isLast); 0317 } else { 0318 Q_EMIT atKeyframe(false, false); 0319 } 0320 if (m_keyframes.contains(m_currentKeyframe.first)) { 0321 // bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey(); 0322 // Q_EMIT atKeyframe(true, isLast); 0323 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 0324 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 0325 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 0326 } else { 0327 m_currentKeyframe = {-1, -1}; 0328 Q_EMIT selectedKf(m_currentKeyframe, {-1, -1}); 0329 } 0330 } 0331 int maxWidth = width() - (2 * m_offset); 0332 m_scale = maxWidth / double(qMax(1, remapMax())); 0333 m_zoomStart = m_zoomHandle.x() * maxWidth; 0334 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 0335 Q_EMIT updateMaxDuration(); 0336 update(); 0337 } 0338 0339 void RemapView::mouseMoveEvent(QMouseEvent *event) 0340 { 0341 event->accept(); 0342 // Check for start/end snapping on drag 0343 int pos = -1; 0344 if (m_moveKeyframeMode == BottomMove || m_moveKeyframeMode == TopMove) { 0345 double snapPos = ((m_originalRange.first - m_inFrame) * m_scale) - m_zoomStart; 0346 snapPos *= m_zoomFactor; 0347 snapPos += m_offset; 0348 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0349 if (qAbs(snapPos - event->x()) < QApplication::startDragDistance()) { 0350 #else 0351 if (qAbs(snapPos - event->position().x()) < QApplication::startDragDistance()) { 0352 #endif 0353 pos = m_originalRange.first - m_inFrame; 0354 } else { 0355 snapPos = ((m_originalRange.second - m_inFrame) * m_scale) - m_zoomStart; 0356 snapPos *= m_zoomFactor; 0357 snapPos += m_offset; 0358 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0359 if (qAbs(snapPos - event->x()) < QApplication::startDragDistance()) { 0360 #else 0361 if (qAbs(snapPos - event->position().x()) < QApplication::startDragDistance()) { 0362 #endif 0363 pos = m_originalRange.second - m_inFrame; 0364 } 0365 } 0366 if (pos == -1) { 0367 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0368 pos = int(((event->x() - m_offset) / m_zoomFactor + m_zoomStart) / m_scale); 0369 #else 0370 pos = int(((event->position().x() - m_offset) / m_zoomFactor + m_zoomStart) / m_scale); 0371 #endif 0372 } 0373 } else { 0374 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0375 pos = int(((event->x() - m_offset) / m_zoomFactor + m_zoomStart) / m_scale); 0376 #else 0377 pos = int(((event->position().x() - m_offset) / m_zoomFactor + m_zoomStart) / m_scale); 0378 #endif 0379 } 0380 pos = qBound(0, pos, m_maxLength - 1); 0381 GenTime position(pos, pCore->getCurrentFps()); 0382 if (event->buttons() == Qt::NoButton) { 0383 bool hoverKeyframe = false; 0384 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0385 if (event->y() < 2 * m_lineHeight && event->y() > m_lineHeight) { 0386 #else 0387 if (event->position().y() < 2 * m_lineHeight && event->position().y() > m_lineHeight) { 0388 #endif 0389 // mouse move in top keyframes area 0390 std::pair<int, int> keyframe = getClosestKeyframe(pos + m_inFrame); 0391 if (keyframe.first > -1 && qAbs(keyframe.second - pos - m_inFrame) * m_scale * m_zoomFactor <= m_lineHeight / 2) { 0392 hoverKeyframe = true; 0393 } 0394 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0395 } else if (event->y() > m_bottomView - 2 * m_lineHeight && event->y() < m_bottomView - m_lineHeight) { 0396 #else 0397 } else if (event->position().y() > m_bottomView - 2 * m_lineHeight && event->position().y() < m_bottomView - m_lineHeight) { 0398 #endif 0399 // move in bottom keyframe area 0400 std::pair<int, int> keyframe = getClosestKeyframe(pos + m_inFrame, true); 0401 if (keyframe.first > -1 && qAbs(keyframe.first - pos - m_inFrame) * m_scale * m_zoomFactor <= m_lineHeight / 2) { 0402 hoverKeyframe = true; 0403 } 0404 } 0405 if (hoverKeyframe) { 0406 setCursor(Qt::PointingHandCursor); 0407 } else { 0408 setCursor(Qt::ArrowCursor); 0409 } 0410 } else if ((event->buttons() & Qt::LeftButton) != 0u) { 0411 if (m_hoverZoomIn || m_hoverZoomOut || m_hoverZoom) { 0412 // Moving zoom handles 0413 if (m_hoverZoomIn) { 0414 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0415 m_zoomHandle.setX(qMin(qMax(0., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.y() - 0.015)); 0416 #else 0417 m_zoomHandle.setX(qMin(qMax(0., double(event->position().x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.y() - 0.015)); 0418 #endif 0419 int maxWidth = width() - (2 * m_offset); 0420 m_zoomStart = m_zoomHandle.x() * maxWidth; 0421 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 0422 update(); 0423 return; 0424 } 0425 if (m_hoverZoomOut) { 0426 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0427 m_zoomHandle.setY(qMax(qMin(1., double(event->x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.x() + 0.015)); 0428 #else 0429 m_zoomHandle.setY(qMax(qMin(1., double(event->position().x() - m_offset) / (width() - 2 * m_offset)), m_zoomHandle.x() + 0.015)); 0430 #endif 0431 int maxWidth = width() - (2 * m_offset); 0432 m_zoomStart = m_zoomHandle.x() * maxWidth; 0433 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 0434 update(); 0435 return; 0436 } 0437 // moving zoom zone 0438 if (m_hoverZoom) { 0439 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0440 double clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset) - m_clickOffset; 0441 #else 0442 double clickOffset = (double(event->position().x()) - m_offset) / (width() - 2 * m_offset) - m_clickOffset; 0443 #endif 0444 double newX = m_zoomHandle.x() + clickOffset; 0445 if (newX < 0) { 0446 clickOffset = -m_zoomHandle.x(); 0447 newX = 0; 0448 } 0449 double newY = m_zoomHandle.y() + clickOffset; 0450 if (newY > 1) { 0451 clickOffset = 1 - m_zoomHandle.y(); 0452 newY = 1; 0453 newX = m_zoomHandle.x() + clickOffset; 0454 } 0455 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0456 m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset); 0457 #else 0458 m_clickOffset = (double(event->position().x()) - m_offset) / (width() - 2 * m_offset); 0459 #endif 0460 m_zoomHandle = QPointF(newX, newY); 0461 int maxWidth = width() - (2 * m_offset); 0462 m_zoomStart = m_zoomHandle.x() * maxWidth; 0463 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 0464 update(); 0465 } 0466 return; 0467 } 0468 // qDebug()<<"=== MOVING MOUSE: "<<pos<<" = "<<m_currentKeyframe<<", MOVE KFMODE: "<<m_moveKeyframeMode; 0469 if ((m_currentKeyframe.second == pos + m_inFrame && m_moveKeyframeMode == TopMove) || 0470 (m_currentKeyframe.first == pos + m_inFrame && m_moveKeyframeMode == BottomMove)) { 0471 return; 0472 } 0473 if (m_currentKeyframe.first >= 0 && m_moveKeyframeMode != NoMove) { 0474 if (m_moveKeyframeMode == BottomMove) { 0475 // Moving bottom keyframe 0476 if (!m_keyframes.contains(pos + m_inFrame)) { 0477 int delta = pos + m_inFrame - m_currentKeyframe.first; 0478 // Check that the move is possible 0479 QMapIterator<int, int> i(m_selectedKeyframes); 0480 while (i.hasNext()) { 0481 i.next(); 0482 int updatedPos = i.key() + delta; 0483 if (!m_selectedKeyframes.contains(updatedPos) && m_keyframes.contains(updatedPos)) { 0484 // Don't allow moving over another keyframe 0485 qDebug() << "== MOVE ABORTED; OVERLAPPING EXISTING"; 0486 return; 0487 } 0488 } 0489 i.toFront(); 0490 QMap<int, int> updated; 0491 while (i.hasNext()) { 0492 i.next(); 0493 if (i.key() < m_currentKeyframe.first) { 0494 continue; 0495 } 0496 // qDebug()<<"=== MOVING KFR: "<<i.key()<<" > "<<(i.key() + delta); 0497 // m_keyframes.insert(i.key() + delta, i.value()); 0498 updated.insert(i.key() + delta, i.value()); 0499 m_keyframes.remove(i.key()); 0500 } 0501 QMapIterator<int, int> j(updated); 0502 while (j.hasNext()) { 0503 j.next(); 0504 m_keyframes.insert(j.key(), j.value()); 0505 } 0506 QMap<int, int> updatedSelection; 0507 QMapIterator<int, int> k(m_previousSelection); 0508 while (k.hasNext()) { 0509 k.next(); 0510 updatedSelection.insert(k.key() + delta, k.value()); 0511 } 0512 m_previousSelection = updatedSelection; 0513 m_currentKeyframe.first += delta; 0514 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 0515 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 0516 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 0517 0518 m_selectedKeyframes = updated; 0519 Q_EMIT seekToPos(-1, pos); 0520 Q_EMIT updateKeyframes(false); 0521 if (remapMax() > m_lastMaxDuration) { 0522 m_lastMaxDuration = remapMax(); 0523 int maxWidth = width() - (2 * m_offset); 0524 m_scale = maxWidth / double(qMax(1, m_lastMaxDuration)); 0525 m_zoomStart = m_zoomHandle.x() * maxWidth; 0526 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 0527 } 0528 update(); 0529 return; 0530 } else { 0531 qDebug() << "=== KEYFRAME :" << pos << " ALREADY EXISTS"; 0532 } 0533 } else if (m_moveKeyframeMode == TopMove) { 0534 // Moving top keyframe 0535 int realPos = qMax(m_inFrame, pos + m_inFrame); 0536 int delta = realPos - m_currentKeyframe.second; 0537 // Check that the move is possible 0538 QMapIterator<int, int> i(m_selectedKeyframes); 0539 while (i.hasNext()) { 0540 i.next(); 0541 if (i.value() + delta >= m_sourceDuration) { 0542 delta = qMin(delta, m_sourceDuration - i.value() - 1); 0543 realPos = m_currentKeyframe.second + delta; 0544 pos = realPos - m_inFrame; 0545 if (delta == 0) { 0546 pCore->displayMessage(i18n("Cannot move last source keyframe past clip end"), MessageType::ErrorMessage, 500); 0547 return; 0548 } 0549 } 0550 if (i.value() + delta < 0) { 0551 delta = qMax(delta, -i.value()); 0552 realPos = m_currentKeyframe.second + delta; 0553 pos = realPos - m_inFrame; 0554 if (delta == 0) { 0555 pCore->displayMessage(i18n("Cannot move first source keyframe before clip start"), MessageType::ErrorMessage, 500); 0556 return; 0557 } 0558 } 0559 } 0560 i.toFront(); 0561 QMap<int, int> updated; 0562 while (i.hasNext()) { 0563 i.next(); 0564 m_keyframes.insert(i.key(), i.value() + delta); 0565 updated.insert(i.key(), i.value() + delta); 0566 if (i.value() == m_currentKeyframe.second) { 0567 m_currentKeyframe.second = realPos; 0568 } 0569 } 0570 QMap<int, int> updatedSelection; 0571 QMapIterator<int, int> k(m_previousSelection); 0572 while (k.hasNext()) { 0573 k.next(); 0574 updatedSelection.insert(k.key(), k.value() + delta); 0575 } 0576 m_previousSelection = updatedSelection; 0577 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 0578 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 0579 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 0580 m_selectedKeyframes = updated; 0581 slotSetPosition(pos + m_inFrame); 0582 Q_EMIT seekToPos(pos + m_inFrame, -1); 0583 Q_EMIT updateKeyframes(false); 0584 if (remapMax() > m_lastMaxDuration) { 0585 m_lastMaxDuration = remapMax(); 0586 int maxWidth = width() - (2 * m_offset); 0587 m_scale = maxWidth / double(qMax(1, m_lastMaxDuration)); 0588 m_zoomStart = m_zoomHandle.x() * maxWidth; 0589 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 0590 } 0591 update(); 0592 return; 0593 } 0594 } 0595 // Rubberband selection 0596 if (m_clickPoint >= 0) { 0597 m_clickEnd = pos + m_inFrame; 0598 int min = qMin(m_clickPoint, m_clickEnd); 0599 int max = qMax(m_clickPoint, m_clickEnd); 0600 min = qMax(1, min); 0601 m_selectedKeyframes.clear(); 0602 m_currentKeyframeOriginal = m_currentKeyframe = {-1, -1}; 0603 QMapIterator<int, int> i(m_keyframes); 0604 while (i.hasNext()) { 0605 i.next(); 0606 if (i.key() > min && i.key() <= max) { 0607 m_selectedKeyframes.insert(i.key(), i.value()); 0608 } 0609 } 0610 if (!m_selectedKeyframes.isEmpty()) { 0611 m_currentKeyframe = {m_selectedKeyframes.firstKey(), m_selectedKeyframes.value(m_selectedKeyframes.firstKey())}; 0612 m_currentKeyframeOriginal = m_currentKeyframe; 0613 } 0614 update(); 0615 return; 0616 } 0617 0618 if (m_moveKeyframeMode == CursorMove) { 0619 if (pos != m_position) { 0620 slotSetPosition(pos + m_inFrame); 0621 Q_EMIT seekToPos(pos + m_inFrame, -1); 0622 } 0623 } 0624 if (m_moveKeyframeMode == CursorMoveBottom) { 0625 pos = qMin(pos, m_keyframes.lastKey() - m_inFrame); 0626 if (pos != m_bottomPosition) { 0627 m_bottomPosition = pos; 0628 bool isKfr = m_keyframes.contains(m_bottomPosition + m_inFrame); 0629 if (isKfr) { 0630 bool isLast = m_bottomPosition + m_inFrame == m_keyframes.firstKey() || m_bottomPosition + m_inFrame == m_keyframes.lastKey(); 0631 Q_EMIT atKeyframe(isKfr, isLast); 0632 } else { 0633 Q_EMIT atKeyframe(false, false); 0634 } 0635 pos = GenTime(m_remapProps.anim_get_double("time_map", pos + m_inFrame)).frames(pCore->getCurrentFps()); 0636 slotSetPosition(pos); 0637 Q_EMIT seekToPos(-1, m_bottomPosition); 0638 } 0639 } 0640 return; 0641 } 0642 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0643 if (event->y() > m_bottomView) { 0644 // Moving in zoom area 0645 if (qAbs(event->x() - m_offset - (m_zoomHandle.x() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) { 0646 #else 0647 if (event->position().y() > m_bottomView) { 0648 // Moving in zoom area 0649 if (qAbs(event->position().x() - m_offset - (m_zoomHandle.x() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) { 0650 #endif 0651 setCursor(Qt::SizeHorCursor); 0652 m_hoverZoomIn = true; 0653 m_hoverZoomOut = false; 0654 m_hoverZoom = false; 0655 update(); 0656 return; 0657 } 0658 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0659 if (qAbs(event->x() - m_offset - (m_zoomHandle.y() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) { 0660 #else 0661 if (qAbs(event->position().x() - m_offset - (m_zoomHandle.y() * (width() - 2 * m_offset))) < QApplication::startDragDistance()) { 0662 #endif 0663 setCursor(Qt::SizeHorCursor); 0664 m_hoverZoomOut = true; 0665 m_hoverZoomIn = false; 0666 m_hoverZoom = false; 0667 update(); 0668 return; 0669 } 0670 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0671 if (m_zoomHandle != QPointF(0, 1) && event->x() > m_offset + (m_zoomHandle.x() * (width() - 2 * m_offset)) && 0672 event->x() < m_offset + (m_zoomHandle.y() * (width() - 2 * m_offset))) { 0673 #else 0674 if (m_zoomHandle != QPointF(0, 1) && event->position().x() > m_offset + (m_zoomHandle.x() * (width() - 2 * m_offset)) && 0675 event->position().x() < m_offset + (m_zoomHandle.y() * (width() - 2 * m_offset))) { 0676 #endif 0677 setCursor(Qt::PointingHandCursor); 0678 m_hoverZoom = true; 0679 m_hoverZoomIn = false; 0680 m_hoverZoomOut = false; 0681 update(); 0682 return; 0683 } 0684 } 0685 0686 if (m_hoverZoomOut || m_hoverZoomIn || m_hoverZoom) { 0687 m_hoverZoomOut = false; 0688 m_hoverZoomIn = false; 0689 m_hoverZoom = false; 0690 setCursor(Qt::ArrowCursor); 0691 update(); 0692 } 0693 } 0694 0695 int RemapView::position() const 0696 { 0697 return m_position; 0698 } 0699 0700 void RemapView::centerCurrentKeyframe() 0701 { 0702 if (m_currentKeyframe.first == -1) { 0703 // No keyframe selected, abort 0704 return; 0705 } 0706 QMap<int, int> nextKeyframes; 0707 if (m_moveNext) { 0708 QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first); 0709 if (it != m_keyframes.end() && *it != m_keyframes.last()) { 0710 it++; 0711 while (it != m_keyframes.end()) { 0712 nextKeyframes.insert(it.key(), it.value()); 0713 it++; 0714 } 0715 } 0716 } 0717 m_keyframes.remove(m_currentKeyframe.first); 0718 int offset = m_bottomPosition + m_inFrame - m_currentKeyframe.first; 0719 m_currentKeyframe.first = m_bottomPosition + m_inFrame; 0720 if (offset == 0) { 0721 // no move 0722 return; 0723 } 0724 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second); 0725 QMapIterator<int, int> i(nextKeyframes); 0726 while (i.hasNext()) { 0727 i.next(); 0728 m_keyframes.remove(i.key()); 0729 } 0730 i.toFront(); 0731 while (i.hasNext()) { 0732 i.next(); 0733 m_keyframes.insert(i.key() + offset, i.value()); 0734 } 0735 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 0736 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 0737 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 0738 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey(); 0739 Q_EMIT atKeyframe(true, isLast); 0740 Q_EMIT updateKeyframes(true); 0741 update(); 0742 } 0743 0744 void RemapView::centerCurrentTopKeyframe() 0745 { 0746 if (m_currentKeyframe.first == -1) { 0747 // No keyframe selected, abort 0748 return; 0749 } 0750 // std::pair<int,int> range = getRange(m_currentKeyframe); 0751 QMap<int, int> nextKeyframes; 0752 int offset = m_position + m_inFrame - m_currentKeyframe.second; 0753 if (m_moveNext) { 0754 QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first); 0755 if (it != m_keyframes.end() && *it != m_keyframes.last()) { 0756 it++; 0757 while (it != m_keyframes.end()) { 0758 nextKeyframes.insert(it.key(), it.value()); 0759 // Check that the move is possible 0760 if (it.value() + offset >= m_sourceDuration) { 0761 pCore->displayMessage(i18n("Cannot move last source keyframe past clip end"), MessageType::ErrorMessage, 500); 0762 return; 0763 } 0764 if (it.value() + offset < 0) { 0765 pCore->displayMessage(i18n("Cannot move first source keyframe before clip start"), MessageType::ErrorMessage, 500); 0766 return; 0767 } 0768 it++; 0769 } 0770 } 0771 } 0772 m_currentKeyframe.second = m_position + m_inFrame; 0773 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second); 0774 QMapIterator<int, int> i(nextKeyframes); 0775 while (i.hasNext()) { 0776 i.next(); 0777 m_keyframes.insert(i.key(), i.value() + offset); 0778 } 0779 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 0780 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 0781 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 0782 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey(); 0783 Q_EMIT atKeyframe(true, isLast); 0784 Q_EMIT updateKeyframes(true); 0785 update(); 0786 } 0787 0788 std::pair<int, int> RemapView::getClosestKeyframe(int pos, bool bottomKeyframe) const 0789 { 0790 int deltaMin = -1; 0791 std::pair<int, int> result = {-1, -1}; 0792 QMapIterator<int, int> i(m_keyframes); 0793 while (i.hasNext()) { 0794 i.next(); 0795 int val = bottomKeyframe ? i.key() : i.value(); 0796 int delta = qAbs(val - pos); 0797 if (deltaMin == -1 || delta < deltaMin) { 0798 deltaMin = delta; 0799 result = {i.key(), i.value()}; 0800 } 0801 } 0802 return result; 0803 } 0804 0805 void RemapView::mouseReleaseEvent(QMouseEvent *event) 0806 { 0807 event->accept(); 0808 bool keyframesEdited = m_keyframesOrigin != m_keyframes; 0809 if (m_moveKeyframeMode == TopMove || m_moveKeyframeMode == BottomMove) { 0810 // Restore original selection 0811 m_selectedKeyframes = m_previousSelection; 0812 int maxWidth = width() - (2 * m_offset); 0813 m_scale = maxWidth / double(qMax(1, remapMax())); 0814 m_zoomStart = m_zoomHandle.x() * maxWidth; 0815 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 0816 update(); 0817 if (!keyframesEdited) { 0818 Q_EMIT seekToPos(m_currentKeyframeOriginal.second, m_bottomPosition); 0819 } 0820 } 0821 m_moveKeyframeMode = NoMove; 0822 if (keyframesEdited) { 0823 Q_EMIT updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin); 0824 } 0825 } 0826 0827 void RemapView::mousePressEvent(QMouseEvent *event) 0828 { 0829 event->accept(); 0830 m_lastMaxDuration = remapMax(); 0831 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0832 int pos = int(((event->x() - m_offset) / m_zoomFactor + m_zoomStart) / m_scale); 0833 #else 0834 int pos = int(((event->position().x() - m_offset) / m_zoomFactor + m_zoomStart) / m_scale); 0835 #endif 0836 pos = qBound(0, pos, m_maxLength); 0837 m_moveKeyframeMode = NoMove; 0838 m_keyframesOrigin = m_keyframes; 0839 m_oldInFrame = m_inFrame; 0840 if (event->button() == Qt::LeftButton) { 0841 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0842 if (event->y() < m_centerPos) { 0843 #else 0844 if (event->position().y() < m_centerPos) { 0845 #endif 0846 // mouse click in top area 0847 if (event->modifiers() & Qt::ShiftModifier) { 0848 m_clickPoint = pos + m_inFrame; 0849 return; 0850 } 0851 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0852 if (event->y() < 2 * m_lineHeight && event->y() > m_lineHeight) { 0853 #else 0854 if (event->position().y() < 2 * m_lineHeight && event->position().y() > m_lineHeight) { 0855 #endif 0856 std::pair<int, int> keyframe = getClosestKeyframe(pos + m_inFrame); 0857 if (keyframe.first > -1 && qAbs(keyframe.second - (pos + m_inFrame)) * m_scale * m_zoomFactor <= m_lineHeight / 2) { 0858 // Clicked on a top keyframe 0859 m_currentKeyframeOriginal = keyframe; 0860 if (event->modifiers() & Qt::ControlModifier) { 0861 if (m_selectedKeyframes.contains(m_currentKeyframeOriginal.first)) { 0862 m_selectedKeyframes.remove(m_currentKeyframeOriginal.first); 0863 m_currentKeyframeOriginal.first = -1; 0864 } else { 0865 m_selectedKeyframes.insert(m_currentKeyframeOriginal.first, m_currentKeyframeOriginal.second); 0866 } 0867 } else if (!m_selectedKeyframes.contains(m_currentKeyframeOriginal.first)) { 0868 m_selectedKeyframes = {m_currentKeyframeOriginal}; 0869 } 0870 // Select and seek to keyframe 0871 m_currentKeyframe = m_currentKeyframeOriginal; 0872 // Calculate speeds 0873 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 0874 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 0875 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 0876 if (m_currentKeyframeOriginal.first > -1) { 0877 m_moveKeyframeMode = TopMove; 0878 m_previousSelection = m_selectedKeyframes; 0879 if (m_moveNext) { 0880 QMapIterator<int, int> i(m_keyframes); 0881 while (i.hasNext()) { 0882 i.next(); 0883 if (i.key() > m_currentKeyframeOriginal.first) { 0884 m_selectedKeyframes.insert(i.key(), i.value()); 0885 } 0886 } 0887 } 0888 if (KdenliveSettings::keyframeseek()) { 0889 slotSetPosition(m_currentKeyframeOriginal.second); 0890 m_bottomPosition = m_currentKeyframeOriginal.first - m_inFrame; 0891 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey(); 0892 Q_EMIT atKeyframe(true, isLast); 0893 } else { 0894 update(); 0895 } 0896 } else { 0897 update(); 0898 } 0899 return; 0900 } 0901 } 0902 // no keyframe next to mouse 0903 // m_selectedKeyframes.clear(); 0904 // m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1}; 0905 m_moveKeyframeMode = CursorMove; 0906 if (pos != m_position) { 0907 slotSetPosition(pos + m_inFrame); 0908 Q_EMIT seekToPos(pos + m_inFrame, -1); 0909 update(); 0910 } 0911 return; 0912 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0913 } else if (event->y() > m_bottomView) { 0914 #else 0915 } else if (event->position().y() > m_bottomView) { 0916 #endif 0917 // click on zoom area 0918 if (m_hoverZoom) { 0919 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0920 m_clickOffset = (double(event->x()) - m_offset) / (width() - 2 * m_offset); 0921 #else 0922 m_clickOffset = (double(event->position().x()) - m_offset) / (width() - 2 * m_offset); 0923 #endif 0924 } 0925 return; 0926 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0927 } else if (event->y() > m_centerPos && event->y() < m_bottomView) { 0928 #else 0929 } else if (event->position().y() > m_centerPos && event->position().y() < m_bottomView) { 0930 #endif 0931 // click in bottom area 0932 if (event->modifiers() & Qt::ShiftModifier) { 0933 m_clickPoint = pos + m_inFrame; 0934 return; 0935 } 0936 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0937 if (event->y() > (m_bottomView - 2 * m_lineHeight) && (event->y() < m_bottomView - m_lineHeight)) { 0938 #else 0939 if (event->position().y() > (m_bottomView - 2 * m_lineHeight) && (event->position().y() < m_bottomView - m_lineHeight)) { 0940 #endif 0941 std::pair<int, int> keyframe = getClosestKeyframe(pos + m_inFrame, true); 0942 if (keyframe.first > -1 && qAbs(keyframe.first - (pos + m_inFrame)) * m_scale * m_zoomFactor <= m_lineHeight / 2) { 0943 m_currentKeyframeOriginal = keyframe; 0944 if (event->modifiers() & Qt::ControlModifier) { 0945 if (m_selectedKeyframes.contains(m_currentKeyframeOriginal.first)) { 0946 m_selectedKeyframes.remove(m_currentKeyframeOriginal.first); 0947 m_currentKeyframeOriginal.second = -1; 0948 } else { 0949 m_selectedKeyframes.insert(m_currentKeyframeOriginal.first, m_currentKeyframeOriginal.second); 0950 } 0951 } else if (!m_selectedKeyframes.contains(m_currentKeyframeOriginal.first)) { 0952 m_selectedKeyframes = {m_currentKeyframeOriginal}; 0953 } 0954 // Select and seek to keyframe 0955 m_currentKeyframe = m_currentKeyframeOriginal; 0956 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 0957 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 0958 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 0959 if (m_currentKeyframeOriginal.first > -1) { 0960 m_moveKeyframeMode = BottomMove; 0961 m_previousSelection = m_selectedKeyframes; 0962 if (m_moveNext) { 0963 QMapIterator<int, int> i(m_keyframes); 0964 while (i.hasNext()) { 0965 i.next(); 0966 if (i.key() > m_currentKeyframeOriginal.first) { 0967 m_selectedKeyframes.insert(i.key(), i.value()); 0968 } 0969 } 0970 } 0971 if (KdenliveSettings::keyframeseek()) { 0972 m_bottomPosition = m_currentKeyframeOriginal.first - m_inFrame; 0973 int topPos = GenTime(m_remapProps.anim_get_double("time_map", m_currentKeyframeOriginal.first)).frames(pCore->getCurrentFps()); 0974 m_position = topPos - m_inFrame; 0975 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey(); 0976 Q_EMIT atKeyframe(true, isLast); 0977 } else { 0978 update(); 0979 } 0980 } else { 0981 update(); 0982 } 0983 return; 0984 } 0985 } 0986 // no keyframe next to mouse 0987 // m_selectedKeyframes.clear(); 0988 // m_currentKeyframe = m_currentKeyframeOriginal = {-1,-1}; 0989 m_moveKeyframeMode = CursorMoveBottom; 0990 pos = qMin(pos, m_keyframes.lastKey() - m_inFrame); 0991 if (pos != m_bottomPosition) { 0992 m_bottomPosition = pos; 0993 bool isKfr = m_keyframes.contains(m_bottomPosition + m_inFrame); 0994 if (isKfr) { 0995 bool isLast = m_bottomPosition + m_inFrame == m_keyframes.firstKey() || m_bottomPosition + m_inFrame == m_keyframes.lastKey(); 0996 Q_EMIT atKeyframe(isKfr, isLast); 0997 } else { 0998 Q_EMIT atKeyframe(false, false); 0999 } 1000 // slotUpdatePosition(); 1001 Q_EMIT seekToPos(-1, pos); 1002 update(); 1003 } 1004 return; 1005 } 1006 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1007 } else if (event->button() == Qt::RightButton && event->y() > m_bottomView) { 1008 #else 1009 } else if (event->button() == Qt::RightButton && event->position().y() > m_bottomView) { 1010 #endif 1011 // Right click on zoom, switch between no zoom and last zoom status 1012 if (m_zoomHandle == QPointF(0, 1)) { 1013 if (!m_lastZoomHandle.isNull()) { 1014 m_zoomHandle = m_lastZoomHandle; 1015 int maxWidth = width() - (2 * m_offset); 1016 m_zoomStart = m_zoomHandle.x() * maxWidth; 1017 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 1018 update(); 1019 return; 1020 } 1021 } else { 1022 m_lastZoomHandle = m_zoomHandle; 1023 m_zoomHandle = QPointF(0, 1); 1024 int maxWidth = width() - (2 * m_offset); 1025 m_zoomStart = m_zoomHandle.x() * maxWidth; 1026 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 1027 update(); 1028 return; 1029 } 1030 } 1031 if (pos != m_position) { 1032 // Q_EMIT seekToPos(pos); 1033 update(); 1034 } 1035 } 1036 1037 void RemapView::wheelEvent(QWheelEvent *event) 1038 { 1039 if (event->modifiers() & Qt::AltModifier) { 1040 // Alt modifier seems to invert x/y axis 1041 if (event->angleDelta().x() > 0) { 1042 goPrev(); 1043 } else { 1044 goNext(); 1045 } 1046 return; 1047 } 1048 if (event->modifiers() & Qt::ControlModifier) { 1049 int maxWidth = width() - 2 * m_offset; 1050 double scaledPos = m_position * m_scale; 1051 double zoomRange = (m_zoomHandle.y() - m_zoomHandle.x()) * maxWidth; 1052 if (event->angleDelta().y() > 0) { 1053 zoomRange /= 1.5; 1054 } else { 1055 zoomRange *= 1.5; 1056 } 1057 if (zoomRange < 5) { 1058 // Don't allow too small zoombar 1059 return; 1060 } 1061 double length = (scaledPos - zoomRange / 2) / maxWidth; 1062 m_zoomHandle.setX(qMax(0., length)); 1063 if (length < 0) { 1064 m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth - length)); 1065 } else { 1066 m_zoomHandle.setY(qMin(1.0, (scaledPos + zoomRange / 2) / maxWidth)); 1067 } 1068 m_zoomStart = m_zoomHandle.x() * maxWidth; 1069 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 1070 update(); 1071 return; 1072 } 1073 if (event->position().y() < m_bottomView) { 1074 int change = event->angleDelta().y() > 0 ? -1 : 1; 1075 int pos = qBound(0, m_position + change, m_duration - 1); 1076 Q_EMIT seekToPos(pos + m_inFrame, -1); 1077 } else { 1078 // Wheel on zoom bar, scroll 1079 double pos = m_zoomHandle.x(); 1080 double zoomWidth = m_zoomHandle.y() - pos; 1081 int maxWidth = width() - 2 * m_offset; 1082 if (event->angleDelta().y() > 0) { 1083 if (zoomWidth / 2 > pos) { 1084 pos = 0; 1085 } else { 1086 pos -= zoomWidth / 2; 1087 } 1088 } else { 1089 if (pos + zoomWidth + zoomWidth / 2 > 1.) { 1090 pos = 1. - zoomWidth; 1091 } else { 1092 pos += zoomWidth / 2; 1093 } 1094 } 1095 m_zoomHandle = QPointF(pos, pos + zoomWidth); 1096 m_zoomStart = m_zoomHandle.x() * maxWidth; 1097 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 1098 update(); 1099 } 1100 } 1101 1102 void RemapView::slotSetPosition(int pos) 1103 { 1104 if (pos != m_position + m_inFrame) { 1105 m_position = pos - m_inFrame; 1106 // int offset = pCore->getItemIn(m_model->getOwnerId()); 1107 // Q_EMIT atKeyframe(m_keyframes.contains(pos)); 1108 double zoomPos = double(m_position) / m_duration; 1109 if (zoomPos < m_zoomHandle.x()) { 1110 double interval = m_zoomHandle.y() - m_zoomHandle.x(); 1111 zoomPos = qBound(0.0, zoomPos - interval / 5, 1.0); 1112 m_zoomHandle.setX(zoomPos); 1113 m_zoomHandle.setY(zoomPos + interval); 1114 } else if (zoomPos > m_zoomHandle.y()) { 1115 double interval = m_zoomHandle.y() - m_zoomHandle.x(); 1116 zoomPos = qBound(0.0, zoomPos + interval / 5, 1.0); 1117 m_zoomHandle.setX(zoomPos - interval); 1118 m_zoomHandle.setY(zoomPos); 1119 } 1120 update(); 1121 } 1122 } 1123 1124 void RemapView::slotSetBottomPosition(int pos) 1125 { 1126 if (pos < 0 || pos + m_inFrame > m_keyframes.lastKey()) { 1127 pos = -1; 1128 } 1129 if (pos != m_bottomPosition) { 1130 m_bottomPosition = pos; 1131 if (m_bottomPosition > -1) { 1132 bool isKfr = m_keyframes.contains(m_bottomPosition + m_inFrame); 1133 if (isKfr) { 1134 bool isLast = m_bottomPosition + m_inFrame == m_keyframes.firstKey() || m_bottomPosition + m_inFrame == m_keyframes.lastKey(); 1135 Q_EMIT atKeyframe(isKfr, isLast); 1136 } else { 1137 Q_EMIT atKeyframe(false, false); 1138 } 1139 } else { 1140 Q_EMIT atKeyframe(false, false); 1141 } 1142 update(); 1143 } 1144 } 1145 1146 void RemapView::goNext() 1147 { 1148 // Seek to next keyframe 1149 QMapIterator<int, int> i(m_keyframes); 1150 while (i.hasNext()) { 1151 i.next(); 1152 if (i.key() > m_bottomPosition + m_inFrame) { 1153 m_currentKeyframe = {i.key(), i.value()}; 1154 m_selectedKeyframes = {m_currentKeyframe}; 1155 slotSetPosition(i.key()); 1156 m_bottomPosition = m_currentKeyframe.first - m_inFrame; 1157 Q_EMIT seekToPos(i.value(), m_bottomPosition); 1158 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 1159 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 1160 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 1161 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey(); 1162 Q_EMIT atKeyframe(true, isLast); 1163 break; 1164 } 1165 } 1166 } 1167 1168 void RemapView::goPrev() 1169 { 1170 // insert keyframe at interpolated position 1171 bool previousFound = false; 1172 QMap<int, int>::iterator it = m_keyframes.begin(); 1173 while (it.key() < m_bottomPosition + m_inFrame && it != m_keyframes.end()) { 1174 it++; 1175 } 1176 if (it != m_keyframes.end()) { 1177 if (it != m_keyframes.begin()) { 1178 it--; 1179 } 1180 m_currentKeyframe = {it.key(), it.value()}; 1181 m_selectedKeyframes = {m_currentKeyframe}; 1182 slotSetPosition(m_currentKeyframe.second); 1183 m_bottomPosition = m_currentKeyframe.first - m_inFrame; 1184 Q_EMIT seekToPos(m_currentKeyframe.second, m_bottomPosition); 1185 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 1186 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 1187 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 1188 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey(); 1189 Q_EMIT atKeyframe(true, isLast); 1190 previousFound = true; 1191 } 1192 1193 if (!previousFound && !m_keyframes.isEmpty()) { 1194 // We are after the last keyframe 1195 m_currentKeyframe = {m_keyframes.lastKey(), m_keyframes.value(m_keyframes.lastKey())}; 1196 m_selectedKeyframes = {m_currentKeyframe}; 1197 slotSetPosition(m_currentKeyframe.second); 1198 m_bottomPosition = m_currentKeyframe.first - m_inFrame; 1199 Q_EMIT seekToPos(m_currentKeyframe.second, m_bottomPosition); 1200 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 1201 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 1202 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 1203 } 1204 } 1205 1206 void RemapView::updateBeforeSpeed(double speed) 1207 { 1208 QMutexLocker lock(&m_kfrMutex); 1209 QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first); 1210 QMap<int, int> updatedKfrs; 1211 QList<int> toDelete; 1212 if (*it != m_keyframes.first() && it != m_keyframes.end()) { 1213 m_keyframesOrigin = m_keyframes; 1214 it--; 1215 double updatedLength = qFuzzyIsNull(speed) ? 0 : (m_currentKeyframe.second - it.value()) * 100. / speed; 1216 double offset = it.key() + updatedLength - m_currentKeyframe.first; 1217 int offsetInt = int(offset); 1218 int lengthInt = int(updatedLength); 1219 if (offsetInt == 0) { 1220 if (offset < 0) { 1221 offsetInt = -1; 1222 } else { 1223 offsetInt = 1; 1224 } 1225 lengthInt += offsetInt; 1226 } 1227 m_keyframes.remove(m_currentKeyframe.first); 1228 m_currentKeyframe.first = it.key() + lengthInt; 1229 m_keyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second); 1230 m_bottomPosition = m_currentKeyframe.first; 1231 m_selectedKeyframes.clear(); 1232 m_selectedKeyframes.insert(m_currentKeyframe.first, m_currentKeyframe.second); 1233 it = m_keyframes.find(m_currentKeyframe.first); 1234 if (*it != m_keyframes.last()) { 1235 it++; 1236 // Update all keyframes after that so that we don't alter the speeds 1237 while (m_moveNext && it != m_keyframes.end()) { 1238 toDelete << it.key(); 1239 updatedKfrs.insert(it.key() + offsetInt, it.value()); 1240 it++; 1241 } 1242 } 1243 for (int p : qAsConst(toDelete)) { 1244 m_keyframes.remove(p); 1245 } 1246 QMapIterator<int, int> i(updatedKfrs); 1247 while (i.hasNext()) { 1248 i.next(); 1249 m_keyframes.insert(i.key(), i.value()); 1250 } 1251 int maxWidth = width() - (2 * m_offset); 1252 m_scale = maxWidth / double(qMax(1, remapMax())); 1253 m_zoomStart = m_zoomHandle.x() * maxWidth; 1254 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 1255 Q_EMIT updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin); 1256 update(); 1257 } 1258 } 1259 1260 void RemapView::updateAfterSpeed(double speed) 1261 { 1262 QMutexLocker lock(&m_kfrMutex); 1263 m_keyframesOrigin = m_keyframes; 1264 QMap<int, int>::iterator it = m_keyframes.find(m_currentKeyframe.first); 1265 if (*it != m_keyframes.last()) { 1266 it++; 1267 QMap<int, int> updatedKfrs; 1268 QList<int> toDelete; 1269 double updatedLength = (it.value() - m_currentKeyframe.second) * 100. / speed; 1270 double offset = m_currentKeyframe.first + updatedLength - it.key(); 1271 int offsetInt = int(offset); 1272 int lengthInt = int(updatedLength); 1273 if (offsetInt == 0) { 1274 if (offset < 0) { 1275 offsetInt = -1; 1276 } else { 1277 offsetInt = 1; 1278 } 1279 lengthInt += offsetInt; 1280 } 1281 if (m_moveNext) { 1282 while (it != m_keyframes.end()) { 1283 toDelete << it.key(); 1284 updatedKfrs.insert(it.key() + offsetInt, it.value()); 1285 it++; 1286 } 1287 } else { 1288 m_keyframes.insert(m_currentKeyframe.first + lengthInt, it.value()); 1289 m_keyframes.remove(it.key()); 1290 } 1291 // Update all keyframes after that so that we don't alter the speeds 1292 for (int p : qAsConst(toDelete)) { 1293 m_keyframes.remove(p); 1294 } 1295 QMapIterator<int, int> i(updatedKfrs); 1296 while (i.hasNext()) { 1297 i.next(); 1298 m_keyframes.insert(i.key(), i.value()); 1299 } 1300 int maxWidth = width() - (2 * m_offset); 1301 m_scale = maxWidth / double(qMax(1, remapMax())); 1302 m_zoomStart = m_zoomHandle.x() * maxWidth; 1303 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 1304 Q_EMIT updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin); 1305 update(); 1306 } 1307 } 1308 1309 const QString RemapView::getKeyframesData(QMap<int, int> keyframes) const 1310 { 1311 QStringList result; 1312 if (keyframes.isEmpty()) { 1313 keyframes = m_keyframes; 1314 } 1315 QMapIterator<int, int> i(keyframes); 1316 int offset = 0; 1317 Mlt::Properties props; 1318 props.set("_profile", pCore->getProjectProfile().get_profile(), 0); 1319 while (i.hasNext()) { 1320 i.next(); 1321 if (i.key() == keyframes.lastKey()) { 1322 // HACK: we always set last keyframe 1 frame after in MLT to ensure we have a correct last frame 1323 offset = 1; 1324 } 1325 result << QString("%1=%2").arg(props.frames_to_time(i.key() + offset, mlt_time_clock)).arg(GenTime(i.value(), pCore->getCurrentFps()).seconds()); 1326 } 1327 return result.join(QLatin1Char(';')); 1328 } 1329 1330 void RemapView::reloadProducer() 1331 { 1332 if (!m_clip || !m_clip->clipUrl().endsWith(QLatin1String(".mlt"))) { 1333 qDebug() << "==== this is not a playlist clip, aborting"; 1334 return; 1335 } 1336 Mlt::Consumer c(pCore->getProjectProfile(), "xml", m_clip->clipUrl().toUtf8().constData()); 1337 QScopedPointer<Mlt::Service> serv(m_clip->originalProducer()->producer()); 1338 if (serv == nullptr) { 1339 return; 1340 } 1341 qDebug() << "==== GOR PLAYLIST SERVICE: " << serv->type() << " / " << serv->consumer()->type() << ", SAVING TO " << m_clip->clipUrl(); 1342 Mlt::Multitrack s2(*serv.data()); 1343 qDebug() << "==== MULTITRACK: " << s2.count(); 1344 Mlt::Tractor s(pCore->getProjectProfile()); 1345 s.set_track(*s2.track(0), 0); 1346 qDebug() << "==== GOT TRACKS: " << s.count(); 1347 int ignore = s.get_int("ignore_points"); 1348 if (ignore) { 1349 s.set("ignore_points", 0); 1350 } 1351 c.connect(s); 1352 c.set("time_format", "frames"); 1353 c.set("no_meta", 1); 1354 c.set("no_root", 1); 1355 // c.set("no_profile", 1); 1356 c.set("root", "/"); 1357 c.set("store", "kdenlive"); 1358 c.run(); 1359 if (ignore) { 1360 s.set("ignore_points", ignore); 1361 } 1362 } 1363 1364 std::pair<double, double> RemapView::getSpeed(std::pair<int, int> kf) 1365 { 1366 std::pair<double, double> speeds = {-1, -1}; 1367 QMap<int, int>::iterator it = m_keyframes.find(kf.first); 1368 if (it == m_keyframes.end()) { 1369 // Not a keyframe 1370 return speeds; 1371 } 1372 if (*it != m_keyframes.first()) { 1373 it--; 1374 speeds.first = (double)(kf.second - it.value()) / (kf.first - it.key()); 1375 it++; 1376 } 1377 if (*it != m_keyframes.last()) { 1378 it++; 1379 speeds.second = (double)(kf.second - it.value()) / (kf.first - it.key()); 1380 } 1381 return speeds; 1382 } 1383 1384 void RemapView::addKeyframe() 1385 { 1386 // insert or remove keyframe at interpolated position 1387 m_keyframesOrigin = m_keyframes; 1388 if (m_keyframes.contains(m_bottomPosition + m_inFrame)) { 1389 m_keyframes.remove(m_bottomPosition + m_inFrame); 1390 if (m_currentKeyframe.first == m_bottomPosition + m_inFrame) { 1391 m_currentKeyframe = m_currentKeyframeOriginal = {-1, -1}; 1392 Q_EMIT selectedKf(m_currentKeyframe, {-1, -1}); 1393 } 1394 Q_EMIT atKeyframe(false, false); 1395 Q_EMIT updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin); 1396 update(); 1397 return; 1398 } 1399 QMapIterator<int, int> i(m_keyframes); 1400 std::pair<int, int> newKeyframe = {-1, -1}; 1401 std::pair<int, int> previous = {-1, -1}; 1402 newKeyframe.first = m_bottomPosition + m_inFrame; 1403 while (i.hasNext()) { 1404 i.next(); 1405 if (i.key() > m_bottomPosition + m_inFrame) { 1406 if (i.key() == m_keyframes.firstKey()) { 1407 // This is the first keyframe 1408 double ratio = (double)(m_bottomPosition + m_inFrame) / i.key(); 1409 newKeyframe.second = i.value() * ratio; 1410 break; 1411 } else if (previous.first > -1) { 1412 std::pair<int, int> current = {i.key(), i.value()}; 1413 double ratio = (double)(m_bottomPosition + m_inFrame - previous.first) / (current.first - previous.first); 1414 qDebug() << "=== RATIO: " << ratio; 1415 newKeyframe.second = previous.second + (qAbs(current.second - previous.second) * ratio); 1416 break; 1417 } 1418 } 1419 previous = {i.key(), i.value()}; 1420 } 1421 if (newKeyframe.second == -1) { 1422 // We are after the last keyframe 1423 if (m_keyframes.isEmpty()) { 1424 newKeyframe.second = m_position + m_inFrame; 1425 } else { 1426 double ratio = (double)(m_position + m_inFrame - m_keyframes.lastKey()) / (m_duration - m_keyframes.lastKey()); 1427 newKeyframe.second = m_keyframes.value(m_keyframes.lastKey()) + (qAbs(m_duration - m_keyframes.value(m_keyframes.lastKey())) * ratio); 1428 } 1429 } 1430 m_keyframes.insert(newKeyframe.first, newKeyframe.second); 1431 m_currentKeyframe = newKeyframe; 1432 m_selectedKeyframes = {m_currentKeyframe}; 1433 std::pair<double, double> speeds = getSpeed(m_currentKeyframe); 1434 std::pair<bool, bool> atEnd = {m_currentKeyframe.first == m_inFrame, m_currentKeyframe.first == m_keyframes.lastKey()}; 1435 Q_EMIT selectedKf(m_currentKeyframe, speeds, atEnd); 1436 bool isLast = m_currentKeyframe.first == m_keyframes.firstKey() || m_currentKeyframe.first == m_keyframes.lastKey(); 1437 Q_EMIT atKeyframe(true, isLast); 1438 Q_EMIT updateKeyframesWithUndo(m_keyframes, m_keyframesOrigin); 1439 update(); 1440 } 1441 1442 void RemapView::toggleMoveNext(bool moveNext) 1443 { 1444 m_moveNext = moveNext; 1445 // Reset keyframe selection 1446 m_selectedKeyframes.clear(); 1447 } 1448 1449 void RemapView::refreshOnDurationChanged(int remapDuration) 1450 { 1451 if (remapDuration != m_duration) { 1452 m_duration = qMax(remapDuration, remapMax()); 1453 int maxWidth = width() - (2 * m_offset); 1454 m_scale = maxWidth / double(qMax(1, remapMax())); 1455 m_zoomStart = m_zoomHandle.x() * maxWidth; 1456 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 1457 } 1458 } 1459 1460 void RemapView::resizeEvent(QResizeEvent *event) 1461 { 1462 int maxWidth = width() - (2 * m_offset); 1463 m_scale = maxWidth / double(qMax(1, remapMax())); 1464 m_zoomStart = m_zoomHandle.x() * maxWidth; 1465 m_zoomFactor = maxWidth / (m_zoomHandle.y() * maxWidth - m_zoomStart); 1466 QWidget::resizeEvent(event); 1467 update(); 1468 } 1469 1470 void RemapView::paintEvent(QPaintEvent *event) 1471 { 1472 Q_UNUSED(event) 1473 QPalette pal = palette(); 1474 KColorScheme scheme(pal.currentColorGroup(), KColorScheme::Window); 1475 m_colSelected = palette().highlight().color(); 1476 m_colKeyframe = scheme.foreground(KColorScheme::NormalText).color(); 1477 QColor bg = scheme.background(KColorScheme::AlternateBackground).color(); 1478 QStylePainter p(this); 1479 int maxWidth = width() - (2 * m_offset); 1480 int zoomEnd = qCeil(m_zoomHandle.y() * maxWidth); 1481 // Top timeline 1482 p.fillRect(m_offset, 0, maxWidth + 1, m_centerPos, bg); 1483 // Bottom timeline 1484 p.fillRect(m_offset, m_bottomView - m_centerPos, maxWidth + 1, m_centerPos, bg); 1485 /* ticks */ 1486 double fps = pCore->getCurrentFps(); 1487 int maxLength = remapMax(); 1488 if (maxLength == 0) { 1489 return; 1490 } 1491 int displayedLength = int(maxLength / m_zoomFactor / fps); 1492 double factor = 1; 1493 if (displayedLength < 2) { 1494 // 1 frame tick 1495 } else if (displayedLength < 30) { 1496 // 1 sec tick 1497 factor = fps; 1498 } else if (displayedLength < 150) { 1499 // 5 sec tick 1500 factor = 5 * fps; 1501 } else if (displayedLength < 300) { 1502 // 10 sec tick 1503 factor = 10 * fps; 1504 } else if (displayedLength < 900) { 1505 // 30 sec tick 1506 factor = 30 * fps; 1507 } else if (displayedLength < 1800) { 1508 // 1 min. tick 1509 factor = 60 * fps; 1510 } else if (displayedLength < 9000) { 1511 // 5 min tick 1512 factor = 300 * fps; 1513 } else if (displayedLength < 18000) { 1514 // 10 min tick 1515 factor = 600 * fps; 1516 } else { 1517 // 30 min tick 1518 factor = 1800 * fps; 1519 } 1520 1521 // Position of left border in frames 1522 double tickOffset = m_zoomStart * m_zoomFactor; 1523 double frameSize = factor * m_scale * m_zoomFactor; 1524 int base = int(tickOffset / frameSize); 1525 tickOffset = frameSize - (tickOffset - (base * frameSize)); 1526 // Draw frame ticks 1527 for (int i = 0; i < maxWidth / frameSize; i++) { 1528 int scaledTick = int(m_offset + (i * frameSize) + tickOffset); 1529 if (scaledTick >= maxWidth + m_offset) { 1530 break; 1531 } 1532 p.drawLine(QPointF(scaledTick, m_lineHeight + 1), QPointF(scaledTick, m_lineHeight - 3)); 1533 p.drawLine(QPointF(scaledTick, m_bottomView - m_lineHeight + 1), QPointF(scaledTick, m_bottomView - m_lineHeight - 3)); 1534 } 1535 1536 /* 1537 * Time-"lines" 1538 * We have a top timeline for the source (clip monitor) and a bottom timeline for the output (project monitor) 1539 */ 1540 p.setPen(m_colKeyframe); 1541 // Top timeline 1542 // qDebug()<<"=== MAX KFR WIDTH: "<<maxWidth<<", DURATION SCALED: "<<(m_duration * m_scale)<<", POS: "<<(m_position * m_scale); 1543 int maxPos = maxWidth + m_offset - 1; 1544 p.drawLine(m_offset, m_lineHeight, maxPos, m_lineHeight); 1545 p.drawLine(m_offset, m_lineHeight - m_lineHeight / 4, m_offset, m_lineHeight + m_lineHeight / 4); 1546 p.drawLine(maxPos, m_lineHeight - m_lineHeight / 4, maxPos, m_lineHeight + m_lineHeight / 4); 1547 // Bottom timeline 1548 p.drawLine(m_offset, m_bottomView - m_lineHeight, maxPos, m_bottomView - m_lineHeight); 1549 p.drawLine(m_offset, m_bottomView - m_lineHeight - m_lineHeight / 4, m_offset, m_bottomView - m_lineHeight + m_lineHeight / 4); 1550 p.drawLine(maxPos, m_bottomView - m_lineHeight - m_lineHeight / 4, maxPos, m_bottomView - m_lineHeight + m_lineHeight / 4); 1551 1552 /* 1553 * Original clip in/out 1554 */ 1555 p.setPen(palette().mid().color()); 1556 double inPos = (double)(m_originalRange.first - m_inFrame) * m_scale; 1557 double outPos = (double)(m_originalRange.second - m_inFrame) * m_scale; 1558 inPos -= m_zoomStart; 1559 inPos *= m_zoomFactor; 1560 outPos -= m_zoomStart; 1561 outPos *= m_zoomFactor; 1562 if (inPos >= 0) { 1563 inPos += m_offset; 1564 p.drawLine(inPos, m_lineHeight, inPos, m_bottomView - m_lineHeight); 1565 } 1566 if (outPos <= maxWidth) { 1567 outPos += m_offset; 1568 p.drawLine(outPos, m_lineHeight, outPos, m_bottomView - m_lineHeight); 1569 } 1570 1571 /* 1572 * Keyframes 1573 */ 1574 QMapIterator<int, int> i(m_keyframes); 1575 while (i.hasNext()) { 1576 i.next(); 1577 double kfOutPos = (double)(i.key() - m_inFrame) * m_scale; 1578 double kfInPos = (double)(i.value() - m_inFrame) * m_scale; 1579 if ((kfInPos < m_zoomStart && kfOutPos < m_zoomStart) || (qFloor(kfInPos) > zoomEnd && qFloor(kfOutPos) > zoomEnd)) { 1580 continue; 1581 } 1582 if (m_currentKeyframe.first == i.key()) { 1583 p.setPen(Qt::red); 1584 p.setBrush(Qt::darkRed); 1585 } else if (m_selectedKeyframes.contains(i.key())) { 1586 p.setPen(m_colSelected); 1587 p.setBrush(m_colSelected); 1588 } else { 1589 p.setPen(m_colKeyframe); 1590 p.setBrush(m_colKeyframe); 1591 } 1592 kfInPos -= m_zoomStart; 1593 kfInPos *= m_zoomFactor; 1594 kfInPos += m_offset; 1595 kfOutPos -= m_zoomStart; 1596 kfOutPos *= m_zoomFactor; 1597 kfOutPos += m_offset; 1598 1599 p.drawLine(kfInPos, m_lineHeight + m_lineHeight * 0.75, kfOutPos, m_bottomView - m_lineHeight * 1.75); 1600 p.drawLine(kfInPos, m_lineHeight, kfInPos, m_lineHeight + m_lineHeight / 2); 1601 p.drawLine(kfOutPos, m_bottomView - m_lineHeight, kfOutPos, m_bottomView - m_lineHeight * 1.5); 1602 p.drawEllipse(QRectF(kfInPos - m_lineHeight / 4.0, m_lineHeight + m_lineHeight / 2, m_lineHeight / 2, m_lineHeight / 2)); 1603 p.drawEllipse(QRectF(kfOutPos - m_lineHeight / 4.0, m_bottomView - 2 * m_lineHeight, m_lineHeight / 2, m_lineHeight / 2)); 1604 } 1605 1606 /* 1607 * current position cursor 1608 */ 1609 p.setPen(m_colSelected); 1610 // Top seek cursor 1611 if (m_position >= 0 && m_position < m_duration) { 1612 p.setBrush(m_colSelected); 1613 double scaledPos = m_position * m_scale; 1614 scaledPos -= m_zoomStart; 1615 scaledPos *= m_zoomFactor; 1616 scaledPos += m_offset; 1617 if (scaledPos >= m_offset && qFloor(scaledPos) <= m_offset + maxWidth) { 1618 QPolygonF topCursor; 1619 topCursor << QPointF(-int(m_lineHeight / 3), -m_lineHeight * 0.5) << QPointF(int(m_lineHeight / 3), -m_lineHeight * 0.5) << QPointF(0, 0); 1620 topCursor.translate(scaledPos, m_lineHeight); 1621 p.drawPolygon(topCursor); 1622 } 1623 } 1624 1625 if (m_bottomPosition >= 0 && m_bottomPosition < m_duration) { 1626 p.setBrush(m_colSelected); 1627 double scaledPos = -1; 1628 if (!m_keyframes.isEmpty()) { 1629 int topPos = GenTime(m_remapProps.anim_get_double("time_map", m_bottomPosition + m_inFrame)).frames(pCore->getCurrentFps()) - m_inFrame; 1630 scaledPos = topPos * m_scale; 1631 scaledPos -= m_zoomStart; 1632 scaledPos *= m_zoomFactor; 1633 scaledPos += m_offset; 1634 } 1635 double scaledPos2 = m_bottomPosition * m_scale; 1636 scaledPos2 -= m_zoomStart; 1637 scaledPos2 *= m_zoomFactor; 1638 scaledPos2 += m_offset; 1639 if (scaledPos2 >= m_offset && qFloor(scaledPos2) <= m_offset + maxWidth) { 1640 QPolygonF bottomCursor; 1641 bottomCursor << QPointF(-int(m_lineHeight / 3), m_lineHeight * 0.5) << QPointF(int(m_lineHeight / 3), m_lineHeight * 0.5) << QPointF(0, 0); 1642 bottomCursor.translate(scaledPos2, m_bottomView - m_lineHeight); 1643 p.setBrush(m_colSelected); 1644 p.drawPolygon(bottomCursor); 1645 } 1646 if (scaledPos > -1) { 1647 p.drawLine(scaledPos, m_lineHeight * 1.75, scaledPos2, m_bottomView - (m_lineHeight * 1.75)); 1648 p.drawLine(scaledPos, m_lineHeight, scaledPos, m_lineHeight * 1.75); 1649 } 1650 p.drawLine(scaledPos2, m_bottomView - m_lineHeight, scaledPos2, m_bottomView - m_lineHeight * 1.75); 1651 } 1652 1653 // Zoom bar 1654 p.setPen(Qt::NoPen); 1655 p.setBrush(palette().mid()); 1656 p.drawRoundedRect(0, m_bottomView + 2, width() - 2 * 0, m_zoomHeight, m_lineHeight / 3, m_lineHeight / 3); 1657 p.setBrush(palette().highlight()); 1658 p.drawRoundedRect(int((width()) * m_zoomHandle.x()), m_bottomView + 2, int((width()) * (m_zoomHandle.y() - m_zoomHandle.x())), m_zoomHeight, 1659 m_lineHeight / 3, m_lineHeight / 3); 1660 } 1661 1662 TimeRemap::TimeRemap(QWidget *parent) 1663 : QWidget(parent) 1664 , m_cid(-1) 1665 { 1666 setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 1667 setupUi(this); 1668 warningMessage->hide(); 1669 QAction *ac = new QAction(i18n("Transcode"), this); 1670 warningMessage->addAction(ac); 1671 connect(ac, &QAction::triggered, this, [&]() { 1672 QMetaObject::invokeMethod(pCore->bin(), "requestTranscoding", Qt::QueuedConnection, Q_ARG(QString, QString()), Q_ARG(QString, m_binId), Q_ARG(int, 0), 1673 Q_ARG(bool, false)); 1674 }); 1675 m_view = new RemapView(this); 1676 speedBefore->setKeyboardTracking(false); 1677 speedAfter->setKeyboardTracking(false); 1678 remapLayout->addWidget(m_view); 1679 connect(m_view, &RemapView::selectedKf, this, [this](std::pair<int, int> selection, std::pair<double, double> speeds, std::pair<bool, bool> atEnd) { 1680 info_frame->setEnabled(selection.first > -1); 1681 QSignalBlocker bk(m_in); 1682 QSignalBlocker bk2(m_out); 1683 m_in->setValue(selection.second - m_view->m_inFrame); 1684 m_out->setValue(selection.first - m_view->m_inFrame); 1685 QSignalBlocker bk3(speedBefore); 1686 QSignalBlocker bk4(speedAfter); 1687 speedBefore->setEnabled(!atEnd.first); 1688 speedBefore->setValue(100. * speeds.first); 1689 speedAfter->setEnabled(!atEnd.second); 1690 speedAfter->setValue(100. * speeds.second); 1691 }); 1692 connect(m_view, &RemapView::updateSpeeds, this, [this](std::pair<double, double> speeds) { 1693 QSignalBlocker bk3(speedBefore); 1694 QSignalBlocker bk4(speedAfter); 1695 speedBefore->setEnabled(speeds.first > 0); 1696 speedBefore->setValue(100. * speeds.first); 1697 speedAfter->setEnabled(speeds.second > 0); 1698 speedAfter->setValue(100. * speeds.second); 1699 }); 1700 button_add->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-add"))); 1701 button_add->setToolTip(i18n("Add keyframe")); 1702 button_add->setWhatsThis(xi18nc("@info:whatsthis", "Inserts a keyframe at the current playhead position/frame.")); 1703 button_next->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-next"))); 1704 button_next->setToolTip(i18n("Go to next keyframe")); 1705 button_next->setWhatsThis(xi18nc("@info:whatsthis", "Moves the playhead to the next keyframe to the right.")); 1706 button_prev->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-previous"))); 1707 button_prev->setToolTip(i18n("Go to previous keyframe")); 1708 button_prev->setWhatsThis(xi18nc("@info:whatsthis", "Moves the playhead to the next keyframe to the left.")); 1709 connect(m_view, &RemapView::updateKeyframes, this, &TimeRemap::updateKeyframes); 1710 connect(m_view, &RemapView::updateKeyframesWithUndo, this, &TimeRemap::updateKeyframesWithUndo); 1711 connect(m_in, &TimecodeDisplay::timeCodeUpdated, this, [this]() { m_view->updateInPos(m_in->getValue() + m_view->m_inFrame); }); 1712 button_center->setToolTip(i18n("Move selected keyframe to cursor")); 1713 button_center->setWhatsThis(xi18nc("@info:whatsthis", "Moves the selected keyframes to the current playhead position/frame.")); 1714 button_center_top->setToolTip(i18n("Move selected keyframe to cursor")); 1715 button_center_top->setWhatsThis(xi18nc("@info:whatsthis", "Moves the selected keyframes to the current playhead position/frame.")); 1716 connect(m_out, &TimecodeDisplay::timeCodeUpdated, this, [this]() { m_view->updateOutPos(m_out->getValue() + m_view->m_inFrame); }); 1717 connect(button_center, &QToolButton::clicked, m_view, &RemapView::centerCurrentKeyframe); 1718 connect(button_center_top, &QToolButton::clicked, m_view, &RemapView::centerCurrentTopKeyframe); 1719 connect(m_view, &RemapView::atKeyframe, button_add, [&](bool atKeyframe, bool last) { 1720 if (atKeyframe) { 1721 button_add->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-remove"))); 1722 button_add->setToolTip(i18n("Delete keyframe")); 1723 button_add->setWhatsThis(xi18nc("@info:whatsthis", "Deletes the keyframe at the current position of the playhead.")); 1724 } else { 1725 button_add->setIcon(QIcon::fromTheme(QStringLiteral("keyframe-add"))); 1726 button_add->setToolTip(i18n("Add keyframe")); 1727 button_add->setWhatsThis(xi18nc("@info:whatsthis", "Inserts a keyframe at the current playhead position/frame.")); 1728 } 1729 button_add->setEnabled(!atKeyframe || !last); 1730 }); 1731 connect(speedBefore, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [&](double speed) { m_view->updateBeforeSpeed(speed); }); 1732 connect(speedAfter, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [&](double speed) { m_view->updateAfterSpeed(speed); }); 1733 connect(button_del, &QToolButton::clicked, this, [this]() { 1734 if (m_cid > -1) { 1735 std::shared_ptr<TimelineItemModel> model = pCore->currentDoc()->getTimeline(m_uuid); 1736 model->requestClipTimeRemap(m_cid, false); 1737 selectedClip(-1, QUuid()); 1738 } 1739 }); 1740 connect(button_add, &QToolButton::clicked, m_view, &RemapView::addKeyframe); 1741 connect(button_next, &QToolButton::clicked, m_view, &RemapView::goNext); 1742 connect(button_prev, &QToolButton::clicked, m_view, &RemapView::goPrev); 1743 connect(move_next, &QCheckBox::toggled, m_view, &RemapView::toggleMoveNext); 1744 connect(pitch_compensate, &QCheckBox::toggled, this, &TimeRemap::switchRemapParam); 1745 connect(frame_blending, &QCheckBox::toggled, this, &TimeRemap::switchRemapParam); 1746 connect(m_view, &RemapView::updateMaxDuration, this, [this]() { 1747 m_out->setRange(0, INT_MAX); 1748 // m_in->setRange(0, duration - 1); 1749 }); 1750 remap_box->setEnabled(false); 1751 } 1752 1753 const QString &TimeRemap::currentClip() const 1754 { 1755 return m_binId; 1756 } 1757 1758 void TimeRemap::checkClipUpdate(const QModelIndex &topLeft, const QModelIndex &, const QVector<int> &roles) 1759 { 1760 int id = int(topLeft.internalId()); 1761 if (m_cid != id || !roles.contains(TimelineModel::FinalMoveRole)) { 1762 return; 1763 } 1764 // Don't resize view if we are moving a keyframe 1765 if (!m_view->movingKeyframe()) { 1766 ObjectId oid(KdenliveObjectType::TimelineClip, m_cid, m_uuid); 1767 int newDuration = pCore->getItemDuration(oid); 1768 // Check if the keyframes were modified by an external resize operation 1769 std::shared_ptr<TimelineItemModel> model = pCore->currentDoc()->getTimeline(m_uuid); 1770 std::shared_ptr<ClipModel> clip = model->getClipPtr(m_cid); 1771 QMap<QString, QString> values = clip->getRemapValues(); 1772 if (values.value(QLatin1String("time_map")) == m_view->getKeyframesData()) { 1773 // Resize was triggered by our keyframe move, nothing to do 1774 return; 1775 } 1776 // Reload keyframes 1777 m_lastLength = newDuration; 1778 int min = pCore->getItemIn(oid); 1779 m_remapLink->set("time_map", values.value(QLatin1String("time_map")).toUtf8().constData()); 1780 m_view->m_remapProps.inherit(*m_remapLink.get()); 1781 // This is a fake query to force the animation to be parsed 1782 (void)m_view->m_remapProps.anim_get_double("time_map", 0); 1783 m_view->m_startPos = pCore->getItemPosition(oid); 1784 m_in->setRange(0, m_view->m_maxLength - min); 1785 m_out->setRange(0, INT_MAX); 1786 m_view->loadKeyframes(values.value(QLatin1String("time_map"))); 1787 m_view->update(); 1788 } 1789 } 1790 1791 void TimeRemap::selectedClip(int cid, const QUuid uuid) 1792 { 1793 if (cid == -1 && cid == m_cid) { 1794 warningMessage->hide(); 1795 return; 1796 } 1797 QObject::disconnect(m_seekConnection1); 1798 QObject::disconnect(m_seekConnection3); 1799 disconnect(pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekRemap, m_view, &RemapView::slotSetPosition); 1800 disconnect(pCore->getMonitor(Kdenlive::ProjectMonitor), &Monitor::seekPosition, this, &TimeRemap::monitorSeek); 1801 if (!m_uuid.isNull()) { 1802 std::shared_ptr<TimelineItemModel> previousModel = pCore->currentDoc()->getTimeline(m_uuid); 1803 disconnect(previousModel.get(), &TimelineItemModel::dataChanged, this, &TimeRemap::checkClipUpdate); 1804 } 1805 m_cid = cid; 1806 m_uuid = uuid; 1807 m_remapLink.reset(); 1808 m_splitRemap.reset(); 1809 if (cid == -1 || uuid.isNull()) { 1810 m_binId.clear(); 1811 m_view->setDuration(nullptr, -1); 1812 remap_box->setEnabled(false); 1813 return; 1814 } 1815 m_view->m_remapProps.set("_profile", pCore->getProjectProfile().get_profile(), 0); 1816 connect(pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekRemap, m_view, &RemapView::slotSetPosition, Qt::UniqueConnection); 1817 std::shared_ptr<TimelineItemModel> model = pCore->currentDoc()->getTimeline(uuid); 1818 m_binId = model->getClipBinId(cid); 1819 std::shared_ptr<Mlt::Producer> prod = model->getClipProducer(cid); 1820 // Check for B Frames and warn 1821 if (prod->parent().get_int("meta.media.has_b_frames") == 1) { 1822 m_view->setDuration(nullptr, -1); 1823 remap_box->setEnabled(false); 1824 warningMessage->setText(i18n("Time remap does not work on clip with B frames.")); 1825 warningMessage->animatedShow(); 1826 return; 1827 } else { 1828 warningMessage->hide(); 1829 remap_box->setEnabled(true); 1830 } 1831 m_splitId = model->m_groups->getSplitPartner(cid); 1832 ObjectId oid(KdenliveObjectType::TimelineClip, cid, m_uuid); 1833 m_lastLength = pCore->getItemDuration(oid); 1834 m_view->m_startPos = pCore->getItemPosition(oid); 1835 model->requestClipTimeRemap(cid); 1836 connect(model.get(), &TimelineItemModel::dataChanged, this, &TimeRemap::checkClipUpdate); 1837 m_view->m_maxLength = prod->get_length(); 1838 m_in->setRange(0, m_view->m_maxLength - prod->get_in()); 1839 // m_in->setRange(0, m_lastLength - 1); 1840 m_out->setRange(0, INT_MAX); 1841 m_view->setDuration(prod, m_lastLength, prod->parent().get_length()); 1842 bool remapFound = false; 1843 if (prod->parent().type() == mlt_service_chain_type) { 1844 Mlt::Chain fromChain(prod->parent()); 1845 int count = fromChain.link_count(); 1846 QScopedPointer<Mlt::Link> fromLink; 1847 for (int i = 0; i < count; i++) { 1848 fromLink.reset(fromChain.link(i)); 1849 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) { 1850 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 1851 // Found a timeremap effect, read params 1852 if (!fromLink->property_exists("time_map")) { 1853 fromLink->set("time_map", fromLink->get("map")); 1854 } 1855 QString mapData(fromLink->get("time_map")); 1856 m_remapLink.reset(fromChain.link(i)); 1857 m_view->m_remapProps.inherit(*m_remapLink.get()); 1858 // This is a fake query to force the animation to be parsed 1859 (void)m_view->m_remapProps.anim_get_double("time_map", 0, m_lastLength + prod->get_in()); 1860 m_view->loadKeyframes(mapData); 1861 if (mapData.isEmpty()) { 1862 // We are just adding the remap effect, set default params 1863 if (model->clipIsAudio(cid)) { 1864 fromLink->set("pitch", 1); 1865 } 1866 fromLink->set("image_mode", "nearest"); 1867 } 1868 remapFound = true; 1869 break; 1870 } 1871 } 1872 } 1873 if (remapFound && m_splitId > -1) { 1874 std::shared_ptr<Mlt::Producer> prod2 = model->getClipProducer(m_splitId); 1875 if (prod2->parent().type() == mlt_service_chain_type) { 1876 Mlt::Chain fromChain2(prod2->parent()); 1877 count = fromChain2.link_count(); 1878 for (int j = 0; j < count; j++) { 1879 QScopedPointer<Mlt::Link> fromLink2(fromChain2.link(j)); 1880 if (fromLink2 && fromLink2->is_valid() && fromLink2->get("mlt_service")) { 1881 if (fromLink2->get("mlt_service") == QLatin1String("timeremap")) { 1882 m_splitRemap.reset(fromChain2.link(j)); 1883 break; 1884 } 1885 } 1886 } 1887 } 1888 } 1889 if (remapFound) { 1890 QSignalBlocker bk(pitch_compensate); 1891 QSignalBlocker bk2(frame_blending); 1892 pitch_compensate->setChecked(m_remapLink->get_int("pitch") == 1 || (m_splitRemap && m_splitRemap->get_int("pitch"))); 1893 frame_blending->setChecked(m_remapLink->get("image_mode") != QLatin1String("nearest")); 1894 remap_box->setEnabled(true); 1895 } 1896 } else { 1897 qDebug() << "/// PRODUCER IS NOT A CHAIN!!!!"; 1898 } 1899 if (!m_binId.isEmpty() && pCore->getMonitor(Kdenlive::ClipMonitor)->activeClipId() == m_binId) { 1900 connect(pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekPosition, pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekRemap, 1901 Qt::UniqueConnection); 1902 } 1903 m_seekConnection1 = connect(m_view, &RemapView::seekToPos, this, [this](int topPos, int bottomPos) { 1904 if (topPos > -1) { 1905 if (pCore->getMonitor(Kdenlive::ClipMonitor)->activeClipId() != m_binId) { 1906 int min = pCore->getItemIn(ObjectId(KdenliveObjectType::TimelineClip, m_cid, m_uuid)); 1907 int lastLength = pCore->getItemDuration(ObjectId(KdenliveObjectType::TimelineClip, m_cid, m_uuid)); 1908 int max = min + lastLength; 1909 pCore->selectBinClip(m_binId, true, min, {min, max}); 1910 } 1911 pCore->getMonitor(Kdenlive::ClipMonitor)->requestSeekIfVisible(topPos); 1912 } 1913 if (bottomPos > -1) { 1914 pCore->getMonitor(Kdenlive::ProjectMonitor)->requestSeek(bottomPos + m_view->m_startPos); 1915 } 1916 }); 1917 connect(pCore->getMonitor(Kdenlive::ProjectMonitor), &Monitor::seekPosition, this, &TimeRemap::monitorSeek, Qt::UniqueConnection); 1918 } 1919 1920 void TimeRemap::monitorSeek(int pos) 1921 { 1922 m_view->slotSetBottomPosition(pos - m_view->m_startPos); 1923 } 1924 1925 void TimeRemap::setClip(std::shared_ptr<ProjectClip> clip, int in, int out) 1926 { 1927 if (m_cid > -1 && clip == nullptr) { 1928 return; 1929 } 1930 QObject::disconnect(m_seekConnection1); 1931 QObject::disconnect(m_seekConnection2); 1932 QObject::disconnect(m_seekConnection3); 1933 m_cid = -1; 1934 m_splitId = -1; 1935 m_uuid = QUuid(); 1936 m_binId.clear(); 1937 m_remapLink.reset(); 1938 m_splitRemap.reset(); 1939 if (clip == nullptr || !clip->statusReady() || clip->clipType() != ClipType::Playlist) { 1940 m_view->setDuration(nullptr, -1); 1941 remap_box->setEnabled(false); 1942 return; 1943 } 1944 bool keyframesLoaded = false; 1945 int min = in == -1 ? 0 : in; 1946 int max = out == -1 ? clip->getFramePlaytime() : out; 1947 m_in->setRange(0, max - min); 1948 m_out->setRange(min, INT_MAX); 1949 m_view->m_startPos = 0; 1950 m_view->setBinClipDuration(clip, max - min); 1951 if (clip->clipType() == ClipType::Playlist) { 1952 std::unique_ptr<Mlt::Service> service(clip->originalProducer()->producer()); 1953 qDebug() << "==== producer type: " << service->type(); 1954 if (service->type() == mlt_service_multitrack_type) { 1955 Mlt::Multitrack multi(*service.get()); 1956 for (int i = 0; i < multi.count(); i++) { 1957 std::unique_ptr<Mlt::Producer> track(multi.track(i)); 1958 qDebug() << "==== GOT TRACK TYPE: " << track->type(); 1959 switch (track->type()) { 1960 case mlt_service_chain_type: { 1961 Mlt::Chain fromChain(*track.get()); 1962 int count = fromChain.link_count(); 1963 for (int j = 0; j < count; j++) { 1964 QScopedPointer<Mlt::Link> fromLink(fromChain.link(j)); 1965 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) { 1966 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 1967 // Found a timeremap effect, read params 1968 if (!fromLink->property_exists("time_map")) { 1969 fromLink->set("time_map", fromLink->get("map")); 1970 } 1971 m_remapLink.reset(fromChain.link(j)); 1972 m_view->m_remapProps.inherit(*m_remapLink.get()); 1973 // This is a fake query to force the animation to be parsed 1974 (void)m_view->m_remapProps.anim_get_double("time_map", 0); 1975 QString mapData(fromLink->get("time_map")); 1976 m_view->loadKeyframes(mapData); 1977 keyframesLoaded = true; 1978 break; 1979 } 1980 } 1981 } 1982 break; 1983 } 1984 case mlt_service_playlist_type: { 1985 // that is a single track 1986 Mlt::Playlist local_playlist(*track); 1987 int count = local_playlist.count(); 1988 qDebug() << "==== PLAYLIST COUNT: " << count; 1989 if (count == 1) { 1990 std::unique_ptr<Mlt::Producer> playlistEntry(local_playlist.get_clip(0)); 1991 Mlt::Producer prod = playlistEntry->parent(); 1992 qDebug() << "==== GOT PROD TYPE: " << prod.type() << " = " << prod.get("mlt_service") << " = " << prod.get("resource"); 1993 if (prod.type() == mlt_service_chain_type) { 1994 Mlt::Chain fromChain(prod); 1995 int linkCount = fromChain.link_count(); 1996 for (int j = 0; j < linkCount; j++) { 1997 QScopedPointer<Mlt::Link> fromLink(fromChain.link(j)); 1998 if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")) { 1999 if (fromLink->get("mlt_service") == QLatin1String("timeremap")) { 2000 // Found a timeremap effect, read params 2001 if (!fromLink->property_exists("time_map")) { 2002 fromLink->set("time_map", fromLink->get("map")); 2003 } 2004 m_remapLink.reset(fromChain.link(j)); 2005 m_view->m_remapProps.inherit(*m_remapLink.get()); 2006 (void)m_view->m_remapProps.anim_get_double("time_map", 0); 2007 QString mapData(fromLink->get("time_map")); 2008 m_view->loadKeyframes(mapData); 2009 keyframesLoaded = true; 2010 break; 2011 } 2012 } 2013 } 2014 } 2015 } 2016 break; 2017 } 2018 default: 2019 qDebug() << "=== UNHANDLED TRACK TYPE"; 2020 break; 2021 } 2022 } 2023 } 2024 } 2025 if (!keyframesLoaded) { 2026 m_view->loadKeyframes(QString()); 2027 } 2028 m_seekConnection1 = connect(m_view, &RemapView::seekToPos, pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::requestSeek, Qt::UniqueConnection); 2029 m_seekConnection2 = connect(pCore->getMonitor(Kdenlive::ClipMonitor), &Monitor::seekPosition, this, [&](int pos) { m_view->slotSetPosition(pos); }); 2030 remap_box->setEnabled(m_remapLink != nullptr); 2031 } 2032 2033 void TimeRemap::updateKeyframes() 2034 { 2035 QString kfData = m_view->getKeyframesData(); 2036 if (m_remapLink) { 2037 m_remapLink->set("time_map", kfData.toUtf8().constData()); 2038 m_view->m_remapProps.inherit(*m_remapLink.get()); 2039 (void)m_view->m_remapProps.anim_get_double("time_map", 0); 2040 if (m_splitRemap) { 2041 m_splitRemap->set("time_map", kfData.toUtf8().constData()); 2042 } 2043 if (m_cid == -1) { 2044 // This is a playlist clip 2045 m_view->timer.start(); 2046 } 2047 } 2048 } 2049 2050 void TimeRemap::updateKeyframesWithUndo(const QMap<int, int> &updatedKeyframes, const QMap<int, int> &previousKeyframes) 2051 { 2052 if (m_remapLink == nullptr) { 2053 return; 2054 } 2055 bool usePitch = pitch_compensate->isChecked(); 2056 bool useBlend = frame_blending->isChecked(); 2057 bool hadPitch = m_remapLink->get_int("pitch") == 1; 2058 bool splitHadPitch = m_splitRemap && m_splitRemap->get_int("pitch") == 1; 2059 bool hadBlend = m_remapLink->get("image_mode") != QLatin1String("nearest"); 2060 std::shared_ptr<TimelineItemModel> model = pCore->currentDoc()->getTimeline(m_uuid); 2061 bool masterIsAudio = model->clipIsAudio(m_cid); 2062 bool splitIsAudio = model->clipIsAudio(m_splitId); 2063 ObjectId oid(KdenliveObjectType::TimelineClip, m_cid, m_uuid); 2064 bool durationChanged = updatedKeyframes.isEmpty() ? false : updatedKeyframes.lastKey() - pCore->getItemIn(oid) + 1 != pCore->getItemDuration(oid); 2065 int lastFrame = pCore->getItemDuration(oid) + pCore->getItemIn(oid); 2066 Fun undo = []() { return true; }; 2067 Fun redo = []() { return true; }; 2068 if (durationChanged) { 2069 // Resize first so that serialization doesn't cut keyframes 2070 int length = updatedKeyframes.lastKey() - m_view->m_inFrame + 1; 2071 std::shared_ptr<TimelineItemModel> model = pCore->currentDoc()->getTimeline(m_uuid); 2072 model->requestItemResize(m_cid, length, true, true, undo, redo); 2073 if (m_splitId > 0) { 2074 model->requestItemResize(m_splitId, length, true, true, undo, redo); 2075 } 2076 } 2077 2078 Fun local_undo = [this, link = m_remapLink, splitLink = m_splitRemap, previousKeyframes, cid = m_cid, oldIn = m_view->m_oldInFrame, hadPitch, splitHadPitch, 2079 masterIsAudio, splitIsAudio, lastFrame, hadBlend]() { 2080 QString oldKfData; 2081 bool keyframesChanged = false; 2082 if (!previousKeyframes.isEmpty()) { 2083 oldKfData = m_view->getKeyframesData(previousKeyframes); 2084 keyframesChanged = true; 2085 } 2086 if (keyframesChanged) { 2087 link->set("time_map", oldKfData.toUtf8().constData()); 2088 } 2089 if (masterIsAudio) { 2090 link->set("pitch", hadPitch ? 1 : 0); 2091 } 2092 link->set("image_mode", hadBlend ? "blend" : "nearest"); 2093 if (splitLink) { 2094 if (keyframesChanged) { 2095 splitLink->set("time_map", oldKfData.toUtf8().constData()); 2096 } 2097 if (splitIsAudio) { 2098 splitLink->set("pitch", splitHadPitch ? 1 : 0); 2099 } 2100 splitLink->set("image_mode", hadBlend ? "blend" : "nearest"); 2101 } 2102 if (cid == m_cid) { 2103 QSignalBlocker bk(pitch_compensate); 2104 QSignalBlocker bk2(frame_blending); 2105 pitch_compensate->setChecked(hadPitch); 2106 frame_blending->setChecked(hadBlend); 2107 if (keyframesChanged) { 2108 m_lastLength = previousKeyframes.lastKey() - oldIn; 2109 // This clip is currently displayed in remap view 2110 link->set("time_map", oldKfData.toUtf8().constData()); 2111 m_view->m_remapProps.inherit(*link.get()); 2112 (void)m_view->m_remapProps.anim_get_double("time_map", 0); 2113 m_view->loadKeyframes(oldKfData); 2114 update(); 2115 } 2116 } 2117 return true; 2118 }; 2119 2120 Fun local_redo = [this, link = m_remapLink, splitLink = m_splitRemap, updatedKeyframes, cid = m_cid, usePitch, masterIsAudio, splitIsAudio, 2121 in = m_view->m_inFrame, lastFrame, useBlend]() { 2122 QString newKfData; 2123 bool keyframesChanged = false; 2124 if (!updatedKeyframes.isEmpty()) { 2125 newKfData = m_view->getKeyframesData(updatedKeyframes); 2126 keyframesChanged = true; 2127 } 2128 if (keyframesChanged) { 2129 link->set("time_map", newKfData.toUtf8().constData()); 2130 } 2131 if (masterIsAudio) { 2132 link->set("pitch", usePitch ? 1 : 0); 2133 } 2134 link->set("image_mode", useBlend ? "blend" : "nearest"); 2135 if (splitLink) { 2136 if (keyframesChanged) { 2137 splitLink->set("time_map", newKfData.toUtf8().constData()); 2138 } 2139 if (splitIsAudio) { 2140 splitLink->set("pitch", usePitch ? 1 : 0); 2141 } 2142 splitLink->set("image_mode", useBlend ? "blend" : "nearest"); 2143 } 2144 if (cid == m_cid) { 2145 QSignalBlocker bk(pitch_compensate); 2146 QSignalBlocker bk2(frame_blending); 2147 pitch_compensate->setChecked(usePitch); 2148 frame_blending->setChecked(useBlend); 2149 if (keyframesChanged) { 2150 // This clip is currently displayed in remap view 2151 m_lastLength = updatedKeyframes.lastKey() - in; 2152 link->set("time_map", newKfData.toUtf8().constData()); 2153 m_view->m_remapProps.inherit(*link.get()); 2154 (void)m_view->m_remapProps.anim_get_double("time_map", 0); 2155 m_view->loadKeyframes(newKfData); 2156 update(); 2157 } 2158 } 2159 return true; 2160 }; 2161 local_redo(); 2162 UPDATE_UNDO_REDO_NOLOCK(redo, undo, local_undo, local_redo); 2163 pCore->pushUndo(local_undo, local_redo, i18n("Edit Timeremap keyframes")); 2164 } 2165 2166 void TimeRemap::switchRemapParam() 2167 { 2168 updateKeyframesWithUndo(QMap<int, int>(), QMap<int, int>()); 2169 } 2170 2171 bool TimeRemap::isInRange() const 2172 { 2173 return m_cid != -1 && m_view->isInRange(); 2174 } 2175 2176 TimeRemap::~TimeRemap() 2177 { 2178 // delete m_previewTimer; 2179 }