File indexing completed on 2024-04-28 08:43:28
0001 /* 0002 SPDX-FileCopyrightText: 2012 Till Theato <root@ttill.de> 0003 SPDX-FileCopyrightText: 2014 Jean-Baptiste Mardelle <jb@kdenlive.org> 0004 This file is part of Kdenlive. See www.kdenlive.org. 0005 0006 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0007 */ 0008 0009 #include "bin.h" 0010 #include "bincommands.h" 0011 #include "clipcreator.hpp" 0012 #include "core.h" 0013 #include "dialogs/clipcreationdialog.h" 0014 #include "dialogs/clipjobmanager.h" 0015 #include "dialogs/kdenlivesettingsdialog.h" 0016 #include "dialogs/textbasededit.h" 0017 #include "dialogs/timeremap.h" 0018 #include "doc/documentchecker.h" 0019 #include "doc/docundostack.hpp" 0020 #include "doc/kdenlivedoc.h" 0021 #include "doc/kthumb.h" 0022 #include "effects/effectstack/model/effectstackmodel.hpp" 0023 #include "glaxnimatelauncher.h" 0024 #include "jobs/abstracttask.h" 0025 #include "jobs/audiolevelstask.h" 0026 #include "jobs/cliploadtask.h" 0027 #include "jobs/taskmanager.h" 0028 #include "jobs/transcodetask.h" 0029 #include "kdenlive_debug.h" 0030 #include "kdenlivesettings.h" 0031 #include "macros.hpp" 0032 #include "mainwindow.h" 0033 #include "mediabrowser.h" 0034 #include "mlt++/Mlt.h" 0035 #include "mltcontroller/clipcontroller.h" 0036 #include "mltcontroller/clippropertiescontroller.h" 0037 #include "monitor/monitor.h" 0038 #include "monitor/monitormanager.h" 0039 #include "profiles/profilemodel.hpp" 0040 #include "project/dialogs/guideslist.h" 0041 #include "project/dialogs/slideshowclip.h" 0042 #include "project/invaliddialog.h" 0043 #include "project/projectmanager.h" 0044 #include "project/transcodeseek.h" 0045 #include "projectclip.h" 0046 #include "projectfolder.h" 0047 #include "projectitemmodel.h" 0048 #include "projectsortproxymodel.h" 0049 #include "projectsubclip.h" 0050 #include "tagwidget.hpp" 0051 #include "titler/titlewidget.h" 0052 #include "ui_newtimeline_ui.h" 0053 #include "ui_qtextclip_ui.h" 0054 #include "undohelper.hpp" 0055 #include "utils/thumbnailcache.hpp" 0056 #include "xml/xml.hpp" 0057 0058 #include "utils/KMessageBox_KdenliveCompat.h" 0059 #include <KActionMenu> 0060 #include <KColorScheme> 0061 #include <KIO/ApplicationLauncherJob> 0062 #include <KIO/FileCopyJob> 0063 #include <KIO/OpenFileManagerWindowJob> 0064 #include <KIconEffect> 0065 #include <KIconTheme> 0066 #include <KMessageBox> 0067 #include <KOpenWithDialog> 0068 #include <KRatingPainter> 0069 #include <KService> 0070 #include <KUrlRequesterDialog> 0071 #include <KXMLGUIFactory> 0072 #include <kwidgetsaddons_version.h> 0073 0074 #include <QCryptographicHash> 0075 #include <QDrag> 0076 #include <QFile> 0077 #include <QMenu> 0078 #include <QMimeData> 0079 #include <QSlider> 0080 #include <QStyledItemDelegate> 0081 #include <QTimeLine> 0082 #include <QToolBar> 0083 #include <QUndoCommand> 0084 #include <QUrl> 0085 #include <QVBoxLayout> 0086 0087 #include <memory> 0088 #include <utility> 0089 0090 static QImage m_videoIcon; 0091 static QImage m_audioIcon; 0092 static QImage m_audioUsedIcon; 0093 static QImage m_videoUsedIcon; 0094 static QSize m_iconSize; 0095 static QIcon m_folderIcon; 0096 static QIcon m_sequenceFolderIcon; 0097 0098 /** 0099 * @class BinItemDelegate 0100 * @brief This class is responsible for drawing items in the QTreeView. 0101 */ 0102 class BinItemDelegate : public QStyledItemDelegate 0103 { 0104 public: 0105 explicit BinItemDelegate(QObject *parent = nullptr) 0106 : QStyledItemDelegate(parent) 0107 0108 { 0109 connect(this, &QStyledItemDelegate::closeEditor, [&]() { m_editorOpen = false; }); 0110 } 0111 void setEditorData(QWidget *w, const QModelIndex &i) const override 0112 { 0113 if (!m_editorOpen) { 0114 QStyledItemDelegate::setEditorData(w, i); 0115 m_editorOpen = true; 0116 } 0117 } 0118 bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override 0119 { 0120 if (event->type() == QEvent::MouseButtonPress) { 0121 auto *me = static_cast<QMouseEvent *>(event); 0122 if (index.column() == 0) { 0123 QPoint pos = me->pos(); 0124 if (m_audioDragRect.contains(pos)) { 0125 dragType = PlaylistState::AudioOnly; 0126 } else if (m_videoDragRect.contains(pos)) { 0127 dragType = PlaylistState::VideoOnly; 0128 } else { 0129 dragType = PlaylistState::Disabled; 0130 } 0131 } else { 0132 dragType = PlaylistState::Disabled; 0133 if (index.column() == 7) { 0134 // Rating 0135 QRect rect = option.rect; 0136 rect.adjust(option.rect.width() / 12, 0, 0, 0); 0137 int rate = 0; 0138 if (me->pos().x() > rect.x()) { 0139 rate = KRatingPainter::getRatingFromPosition(rect, Qt::AlignLeft | Qt::AlignVCenter, qApp->layoutDirection(), me->pos()); 0140 } 0141 if (rate > -1) { 0142 // Full star rating only 0143 if (rate % 2 == 1) { 0144 rate++; 0145 } 0146 Q_EMIT static_cast<ProjectSortProxyModel *>(model)->updateRating(index, uint(rate)); 0147 } 0148 } 0149 } 0150 } 0151 event->ignore(); 0152 return false; 0153 } 0154 void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override 0155 { 0156 if (index.column() != 0) { 0157 QStyledItemDelegate::updateEditorGeometry(editor, option, index); 0158 return; 0159 } 0160 QStyleOptionViewItem opt = option; 0161 initStyleOption(&opt, index); 0162 QRect r1 = option.rect; 0163 int type = index.data(AbstractProjectItem::ItemTypeRole).toInt(); 0164 int decoWidth = 0; 0165 if (opt.decorationSize.height() > 0) { 0166 decoWidth += int(r1.height() * pCore->getCurrentDar()); 0167 } 0168 int mid = 0; 0169 if (type == AbstractProjectItem::ClipItem || type == AbstractProjectItem::SubClipItem) { 0170 mid = int((r1.height() / 2)); 0171 } 0172 r1.adjust(decoWidth, 0, 0, -mid); 0173 QFont ft = option.font; 0174 ft.setBold(true); 0175 QFontMetricsF fm(ft); 0176 QRect r2 = fm.boundingRect(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString()).toRect(); 0177 editor->setGeometry(r2); 0178 } 0179 0180 QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override 0181 { 0182 QSize hint = QStyledItemDelegate::sizeHint(option, index); 0183 QString text = index.data(AbstractProjectItem::DataName).toString(); 0184 QRectF r = option.rect; 0185 QFont ft = option.font; 0186 ft.setBold(true); 0187 QFontMetricsF fm(ft); 0188 QStyle *style = option.widget ? option.widget->style() : QApplication::style(); 0189 const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; 0190 int width = int(fm.boundingRect(r, Qt::AlignLeft | Qt::AlignTop, text).width() + option.decorationSize.width()) + 2 * textMargin; 0191 hint.setWidth(width); 0192 int type = index.data(AbstractProjectItem::ItemTypeRole).toInt(); 0193 if (type == AbstractProjectItem::FolderItem) { 0194 return QSize(hint.width(), qMin(option.fontMetrics.lineSpacing() + 4, hint.height())); 0195 } 0196 if (type == AbstractProjectItem::ClipItem) { 0197 return QSize(hint.width(), qMax(option.fontMetrics.lineSpacing() * 2 + 4, qMax(hint.height(), option.decorationSize.height()))); 0198 } 0199 if (type == AbstractProjectItem::SubClipItem) { 0200 return QSize(hint.width(), qMax(option.fontMetrics.lineSpacing() * 2 + 4, qMin(hint.height(), int(option.decorationSize.height() / 1.5)))); 0201 } 0202 QIcon icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole)); 0203 QString line1 = index.data(Qt::DisplayRole).toString(); 0204 QString line2 = index.data(Qt::UserRole).toString(); 0205 0206 int textW = qMax(option.fontMetrics.horizontalAdvance(line1), option.fontMetrics.horizontalAdvance(line2)); 0207 QSize iconSize = icon.actualSize(option.decorationSize); 0208 return {qMax(textW, iconSize.width()) + 4, option.fontMetrics.lineSpacing() * 2 + 4}; 0209 } 0210 0211 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override 0212 { 0213 if (index.column() == 0 && !index.data().isNull()) { 0214 QRect r1 = option.rect; 0215 painter->save(); 0216 painter->setClipRect(r1); 0217 QStyleOptionViewItem opt(option); 0218 initStyleOption(&opt, index); 0219 int type = index.data(AbstractProjectItem::ItemTypeRole).toInt(); 0220 QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); 0221 const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; 0222 // QRect r = QStyle::alignedRect(opt.direction, Qt::AlignVCenter | Qt::AlignLeft, opt.decorationSize, r1); 0223 // Draw alternate background 0224 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); 0225 if ((option.state & static_cast<int>(QStyle::State_Selected)) != 0) { 0226 painter->setPen(option.palette.highlightedText().color()); 0227 } else { 0228 painter->setPen(option.palette.text().color()); 0229 } 0230 QRect r = r1; 0231 QFont font = painter->font(); 0232 font.setBold(true); 0233 painter->setFont(font); 0234 if (type == AbstractProjectItem::ClipItem || type == AbstractProjectItem::SubClipItem) { 0235 int decoWidth = 0; 0236 FileStatus::ClipStatus clipStatus = FileStatus::ClipStatus(index.data(AbstractProjectItem::ClipStatus).toInt()); 0237 int cType = index.data(AbstractProjectItem::ClipType).toInt(); 0238 if (opt.decorationSize.height() > 0) { 0239 r.setWidth(int(r.height() * pCore->getCurrentDar())); 0240 QPixmap pix = opt.icon.pixmap(opt.icon.actualSize(r.size())); 0241 if (!pix.isNull()) { 0242 // Draw icon 0243 decoWidth += r.width() + textMargin; 0244 r.setWidth(r.height() * pix.width() / pix.height()); 0245 painter->drawPixmap(r, pix, QRect(0, 0, pix.width(), pix.height())); 0246 } 0247 m_thumbRect = r; 0248 0249 // Draw frame in case of missing source 0250 if (clipStatus == FileStatus::StatusMissing || clipStatus == FileStatus::StatusProxyOnly) { 0251 painter->save(); 0252 painter->setPen(QPen(clipStatus == FileStatus::StatusProxyOnly ? Qt::yellow : Qt::red, 3)); 0253 painter->drawRect(m_thumbRect.adjusted(0, 0, -1, -1)); 0254 painter->restore(); 0255 } else if (cType == ClipType::Image || cType == ClipType::SlideShow) { 0256 // Draw 'photo' frame to identify image clips 0257 painter->save(); 0258 int penWidth = m_thumbRect.height() / 14; 0259 penWidth += penWidth % 2; 0260 painter->setPen(QPen(QColor(255, 255, 255, 160), penWidth)); 0261 penWidth /= 2; 0262 painter->drawRoundedRect(m_thumbRect.adjusted(penWidth, penWidth, -penWidth - 1, -penWidth - 1), 4, 4); 0263 painter->setPen(QPen(Qt::black, 1)); 0264 painter->drawRoundedRect(m_thumbRect.adjusted(0, 0, -1, -1), 4, 4); 0265 painter->restore(); 0266 } 0267 } 0268 int mid = int((r1.height() / 2)); 0269 r1.adjust(decoWidth, 0, 0, -mid); 0270 QRect r2 = option.rect; 0271 r2.adjust(decoWidth, mid, 0, 0); 0272 QRectF bounding; 0273 painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString(), &bounding); 0274 font.setBold(false); 0275 painter->setFont(font); 0276 QString subText = index.data(AbstractProjectItem::DataDuration).toString(); 0277 QString tags = index.data(AbstractProjectItem::DataTag).toString(); 0278 if (!tags.isEmpty()) { 0279 QStringList t = tags.split(QLatin1Char(';')); 0280 QRectF tagRect = m_thumbRect.adjusted(2, 2, 0, 2); 0281 tagRect.setWidth(r1.height() / 3.5); 0282 tagRect.setHeight(tagRect.width()); 0283 for (const QString &color : qAsConst(t)) { 0284 painter->setBrush(QColor(color)); 0285 painter->drawRoundedRect(tagRect, tagRect.height() / 2, tagRect.height() / 2); 0286 tagRect.moveTop(tagRect.bottom() + tagRect.height() / 4); 0287 } 0288 painter->setBrush(Qt::NoBrush); 0289 } 0290 if (!subText.isEmpty()) { 0291 r2.adjust(0, int(bounding.bottom() - r2.top()), 0, 0); 0292 QColor subTextColor = painter->pen().color(); 0293 bool selected = opt.state & QStyle::State_Selected; 0294 if (!selected) { 0295 subTextColor.setAlphaF(.5); 0296 } 0297 painter->setPen(subTextColor); 0298 // Draw usage counter 0299 const QString usage = index.data(AbstractProjectItem::UsageCount).toString(); 0300 if (!usage.isEmpty()) { 0301 subText.append(QString(" [%1]").arg(usage)); 0302 } 0303 painter->drawText(r2, Qt::AlignLeft | Qt::AlignTop, subText, &bounding); 0304 // Add audio/video icons for selective drag 0305 bool hasAudioAndVideo = index.data(AbstractProjectItem::ClipHasAudioAndVideo).toBool(); 0306 if (hasAudioAndVideo && (cType == ClipType::AV || cType == ClipType::Playlist || cType == ClipType::Timeline)) { 0307 QRect audioRect(0, 0, m_audioIcon.width(), m_audioIcon.height()); 0308 audioRect.moveLeft(bounding.right() + (2 * textMargin) + 1); 0309 audioRect.moveTop(bounding.top() + 1); 0310 QRect videoIconRect = audioRect; 0311 videoIconRect.moveLeft(audioRect.right() + (2 * textMargin)); 0312 if (opt.state & QStyle::State_MouseOver) { 0313 m_audioDragRect = audioRect.adjusted(-1, -1, 1, 1); 0314 m_videoDragRect = videoIconRect.adjusted(-1, -1, 1, 1); 0315 painter->drawImage(audioRect.topLeft(), m_audioIcon); 0316 painter->drawImage(videoIconRect.topLeft(), m_videoIcon); 0317 painter->setPen(opt.palette.highlight().color()); 0318 painter->drawRect(m_audioDragRect); 0319 painter->drawRect(m_videoDragRect); 0320 } else if (!usage.isEmpty()) { 0321 if (index.data(AbstractProjectItem::AudioUsed).toBool()) { 0322 painter->drawImage(audioRect.topLeft(), selected ? m_audioIcon : m_audioUsedIcon); 0323 } 0324 if (index.data(AbstractProjectItem::VideoUsed).toBool()) { 0325 painter->drawImage(videoIconRect.topLeft(), selected ? m_videoIcon : m_videoUsedIcon); 0326 } 0327 } 0328 } /*else if (usage > 0) { 0329 QRect audioRect(0, 0, m_audioIcon.width(), m_audioIcon.height()); 0330 audioRect.moveLeft(bounding.right() + (2 * textMargin) + 1); 0331 audioRect.moveTop(bounding.top() + 1); 0332 QRect videoIconRect = audioRect; 0333 videoIconRect.moveLeft(audioRect.right() + (2 * textMargin)); 0334 int audioUsage = index.data(AbstractProjectItem::AudioUsageCount).toInt(); 0335 if (audioUsage > 0) { 0336 painter->drawImage(audioRect.topLeft(), selected ? m_audioIcon : m_audioUsedIcon); 0337 } 0338 if (usage - audioUsage > 0) { 0339 painter->drawImage(videoIconRect.topLeft(), selected ? m_videoIcon : m_videoUsedIcon); 0340 } 0341 }*/ 0342 } 0343 if (type == AbstractProjectItem::ClipItem) { 0344 // Overlay icon if necessary 0345 QVariant v = index.data(AbstractProjectItem::IconOverlay); 0346 if (!v.isNull()) { 0347 QIcon reload = QIcon::fromTheme(v.toString()); 0348 int size = style->pixelMetric(QStyle::PM_SmallIconSize); 0349 reload.paint(painter, QRect(r.left() + 2, r.bottom() - size - 2, size, size)); 0350 } 0351 int jobProgress = index.data(AbstractProjectItem::JobProgress).toInt(); 0352 auto status = index.data(AbstractProjectItem::JobStatus).value<TaskManagerStatus>(); 0353 if (jobProgress < 100 && (status == TaskManagerStatus::Pending || status == TaskManagerStatus::Running)) { 0354 // Draw job progress bar 0355 int progressWidth = option.fontMetrics.averageCharWidth() * 8; 0356 int progressHeight = option.fontMetrics.ascent() / 4; 0357 QRect progress(r1.x() + 1, opt.rect.bottom() - progressHeight - 2, progressWidth, progressHeight); 0358 painter->setPen(Qt::NoPen); 0359 painter->setBrush(Qt::darkGray); 0360 if (status == TaskManagerStatus::Running) { 0361 painter->drawRoundedRect(progress, 2, 2); 0362 painter->setBrush((option.state & static_cast<int>((QStyle::State_Selected) != 0)) != 0 ? option.palette.text() 0363 : option.palette.highlight()); 0364 progress.setWidth((progressWidth - 2) * jobProgress / 100); 0365 painter->drawRoundedRect(progress, 2, 2); 0366 } else { 0367 // Draw kind of a pause icon 0368 progress.setWidth(3); 0369 painter->drawRect(progress); 0370 progress.moveLeft(progress.right() + 3); 0371 painter->drawRect(progress); 0372 } 0373 } 0374 bool jobsucceeded = index.data(AbstractProjectItem::JobSuccess).toBool(); 0375 if (!jobsucceeded) { 0376 QIcon warning = QIcon::fromTheme(QStringLiteral("process-stop")); 0377 warning.paint(painter, r2); 0378 } 0379 } 0380 } else { 0381 // Folder 0382 int decoWidth = 0; 0383 if (opt.decorationSize.height() > 0) { 0384 QIcon icon = index.data(AbstractProjectItem::SequenceFolder).toBool() ? m_sequenceFolderIcon : m_folderIcon; 0385 r.setWidth(int(r.height() * pCore->getCurrentDar())); 0386 QPixmap pix = icon.pixmap(icon.actualSize(r.size())); 0387 // Draw icon 0388 decoWidth += r.width() + textMargin; 0389 r.setWidth(r.height() * pix.width() / pix.height()); 0390 painter->drawPixmap(r, pix, QRect(0, 0, pix.width(), pix.height())); 0391 } 0392 r1.adjust(decoWidth, 0, 0, 0); 0393 QRectF bounding; 0394 painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString(), &bounding); 0395 } 0396 painter->restore(); 0397 } else if (index.column() == 7) { 0398 // Rating 0399 QStyleOptionViewItem opt(option); 0400 initStyleOption(&opt, index); 0401 QRect r1 = opt.rect; 0402 // Tweak bg opacity since breeze dark star has same color as highlighted background 0403 painter->setOpacity(0.5); 0404 QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); 0405 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); 0406 painter->setOpacity(1); 0407 if (index.data(AbstractProjectItem::ItemTypeRole).toInt() != AbstractProjectItem::FolderItem) { 0408 r1.adjust(r1.width() / 12, 0, 0, 0); 0409 KRatingPainter::paintRating(painter, r1, Qt::AlignLeft | Qt::AlignVCenter, index.data().toInt()); 0410 } 0411 } else { 0412 QStyledItemDelegate::paint(painter, option, index); 0413 } 0414 } 0415 0416 int getFrame(const QModelIndex &index, int mouseX) 0417 { 0418 int type = index.data(AbstractProjectItem::ItemTypeRole).toInt(); 0419 if ((type != AbstractProjectItem::ClipItem && type != AbstractProjectItem::SubClipItem)) { 0420 return 0; 0421 } 0422 if (mouseX < m_thumbRect.x() || mouseX > m_thumbRect.right()) { 0423 return -1; 0424 } 0425 return 100 * (mouseX - m_thumbRect.x()) / m_thumbRect.width(); 0426 } 0427 0428 private: 0429 mutable bool m_editorOpen{false}; 0430 mutable QRect m_audioDragRect; 0431 mutable QRect m_videoDragRect; 0432 mutable QRect m_thumbRect; 0433 0434 public: 0435 PlaylistState::ClipState dragType{PlaylistState::Disabled}; 0436 }; 0437 0438 /** 0439 * @class BinListItemDelegate 0440 * @brief This class is responsible for drawing items in the QListView (Icon view). 0441 */ 0442 0443 class BinListItemDelegate : public QStyledItemDelegate 0444 { 0445 public: 0446 explicit BinListItemDelegate(QObject *parent = nullptr) 0447 : QStyledItemDelegate(parent) 0448 0449 { 0450 connect(this, &QStyledItemDelegate::closeEditor, [&]() { m_editorOpen = false; }); 0451 } 0452 bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override 0453 { 0454 Q_UNUSED(model); 0455 Q_UNUSED(option); 0456 Q_UNUSED(index); 0457 if (event->type() == QEvent::MouseButtonPress) { 0458 auto *me = static_cast<QMouseEvent *>(event); 0459 if (m_audioDragRect.contains(me->pos())) { 0460 dragType = PlaylistState::AudioOnly; 0461 } else if (m_videoDragRect.contains(me->pos())) { 0462 dragType = PlaylistState::VideoOnly; 0463 } else { 0464 dragType = PlaylistState::Disabled; 0465 } 0466 } 0467 event->ignore(); 0468 return false; 0469 } 0470 0471 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override 0472 { 0473 if (!index.data().isNull()) { 0474 QStyleOptionViewItem opt(option); 0475 initStyleOption(&opt, index); 0476 // QStyledItemDelegate::paint(painter, opt, index); 0477 QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); 0478 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); 0479 // style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); 0480 QRect r = opt.rect; 0481 r.setHeight(r.width() / pCore->getCurrentDar()); 0482 int type = index.data(AbstractProjectItem::ItemTypeRole).toInt(); 0483 bool isFolder = type == AbstractProjectItem::FolderItem; 0484 bool isSequenceFolder = index.data(AbstractProjectItem::SequenceFolder).toBool(); 0485 QPixmap pix = isFolder ? (isSequenceFolder ? m_sequenceFolderIcon.pixmap(m_sequenceFolderIcon.actualSize(r.size())) 0486 : m_folderIcon.pixmap(m_folderIcon.actualSize(r.size()))) 0487 : opt.icon.pixmap(opt.icon.actualSize(r.size())); 0488 if (!pix.isNull()) { 0489 // Draw icon 0490 painter->drawPixmap(r, pix, QRect(0, 0, pix.width(), pix.height())); 0491 } 0492 m_thumbRect = r; 0493 QRect textRect = opt.rect; 0494 const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; 0495 textRect.adjust(textMargin, 0, -textMargin, -textMargin); 0496 QRectF bounding; 0497 QString itemText = index.data(AbstractProjectItem::DataName).toString(); 0498 // Draw usage counter 0499 const QString usage = isFolder ? QString() : index.data(AbstractProjectItem::UsageCount).toString(); 0500 if (!usage.isEmpty()) { 0501 int usageWidth = option.fontMetrics.horizontalAdvance(QString(" [%1]").arg(usage)); 0502 int availableWidth = textRect.width() - usageWidth; 0503 if (option.fontMetrics.horizontalAdvance(itemText) > availableWidth) { 0504 itemText = option.fontMetrics.elidedText(itemText, Qt::ElideRight, availableWidth); 0505 } 0506 itemText.append(QString(" [%1]").arg(usage)); 0507 } else { 0508 if (option.fontMetrics.horizontalAdvance(itemText) > textRect.width()) { 0509 itemText = option.fontMetrics.elidedText(itemText, Qt::ElideRight, textRect.width()); 0510 } 0511 } 0512 painter->drawText(textRect, Qt::AlignCenter | Qt::AlignBottom, itemText, &bounding); 0513 0514 if (isFolder) { 0515 return; 0516 } 0517 0518 // Tags 0519 QString tags = index.data(AbstractProjectItem::DataTag).toString(); 0520 if (!tags.isEmpty()) { 0521 QStringList t = tags.split(QLatin1Char(';')); 0522 QRectF tagRect = m_thumbRect.adjusted(2, 2, 0, 2); 0523 tagRect.setWidth(m_thumbRect.height() / 5); 0524 tagRect.setHeight(tagRect.width()); 0525 painter->save(); 0526 for (const QString &color : qAsConst(t)) { 0527 painter->setBrush(QColor(color)); 0528 painter->drawRoundedRect(tagRect, tagRect.height() / 2, tagRect.height() / 2); 0529 tagRect.moveTop(tagRect.bottom() + tagRect.height() / 4); 0530 } 0531 painter->restore(); 0532 } 0533 0534 // Add audio/video icons for selective drag 0535 int cType = index.data(AbstractProjectItem::ClipType).toInt(); 0536 bool hasAudioAndVideo = index.data(AbstractProjectItem::ClipHasAudioAndVideo).toBool(); 0537 if (hasAudioAndVideo && (cType == ClipType::AV || cType == ClipType::Playlist || cType == ClipType::Timeline) && 0538 m_thumbRect.height() > 2.5 * m_audioIcon.height()) { 0539 QRect thumbRect = m_thumbRect; 0540 thumbRect.setLeft(opt.rect.right() - m_audioIcon.width() - 6); 0541 if (opt.state & QStyle::State_MouseOver || !usage.isEmpty()) { 0542 QColor bgColor = option.palette.window().color(); 0543 bgColor.setAlphaF(.7); 0544 painter->fillRect(thumbRect, bgColor); 0545 } 0546 thumbRect.setSize(m_audioIcon.size()); 0547 thumbRect.translate(3, 2); 0548 QRect videoThumbRect = thumbRect; 0549 videoThumbRect.moveTop(thumbRect.bottom() + 2); 0550 if (opt.state & QStyle::State_MouseOver) { 0551 m_audioDragRect = thumbRect; 0552 m_videoDragRect = videoThumbRect; 0553 painter->drawImage(m_audioDragRect.topLeft(), m_audioIcon); 0554 painter->drawImage(m_videoDragRect.topLeft(), m_videoIcon); 0555 } else if (!usage.isEmpty()) { 0556 if (index.data(AbstractProjectItem::AudioUsed).toBool()) { 0557 painter->drawImage(thumbRect.topLeft(), m_audioUsedIcon); 0558 } 0559 if (index.data(AbstractProjectItem::VideoUsed).toBool()) { 0560 painter->drawImage(videoThumbRect.topLeft(), m_videoUsedIcon); 0561 } 0562 } 0563 } 0564 // Draw frame in case of missing source 0565 FileStatus::ClipStatus clipStatus = FileStatus::ClipStatus(index.data(AbstractProjectItem::ClipStatus).toInt()); 0566 if (clipStatus == FileStatus::StatusMissing || clipStatus == FileStatus::StatusProxyOnly) { 0567 painter->save(); 0568 painter->setPen(QPen(clipStatus == FileStatus::StatusProxyOnly ? Qt::yellow : Qt::red, 3)); 0569 painter->drawRect(m_thumbRect); 0570 painter->restore(); 0571 } else if (cType == ClipType::Image || cType == ClipType::SlideShow) { 0572 // Draw 'photo' frame to identify image clips 0573 painter->save(); 0574 int penWidth = m_thumbRect.height() / 14; 0575 penWidth += penWidth % 2; 0576 painter->setPen(QPen(QColor(255, 255, 255, 160), penWidth)); 0577 penWidth /= 2; 0578 painter->drawRoundedRect(m_thumbRect.adjusted(penWidth, penWidth, -penWidth - 1, -penWidth + 1), 4, 4); 0579 painter->setPen(QPen(Qt::black, 1)); 0580 painter->drawRoundedRect(m_thumbRect.adjusted(0, 0, -1, 1), 4, 4); 0581 painter->restore(); 0582 } 0583 // Overlay icon if necessary 0584 QVariant v = index.data(AbstractProjectItem::IconOverlay); 0585 if (!v.isNull()) { 0586 QRect r = m_thumbRect; 0587 QIcon reload = QIcon::fromTheme(v.toString()); 0588 r.setTop(r.bottom() - (opt.rect.height() - r.height())); 0589 r.setWidth(r.height()); 0590 reload.paint(painter, r); 0591 } 0592 int jobProgress = index.data(AbstractProjectItem::JobProgress).toInt(); 0593 auto status = index.data(AbstractProjectItem::JobStatus).value<TaskManagerStatus>(); 0594 if (jobProgress < 100 && (status == TaskManagerStatus::Pending || status == TaskManagerStatus::Running)) { 0595 // Draw job progress bar 0596 int progressHeight = option.fontMetrics.ascent() / 4; 0597 QRect thumbRect = m_thumbRect.adjusted(2, 2, -2, -2); 0598 QRect progress(thumbRect.x(), thumbRect.bottom() - progressHeight - 2, thumbRect.width(), progressHeight); 0599 painter->setPen(Qt::NoPen); 0600 painter->setBrush(Qt::darkGray); 0601 if (status == TaskManagerStatus::Running) { 0602 painter->drawRoundedRect(progress, 2, 2); 0603 painter->setBrush((option.state & static_cast<int>((QStyle::State_Selected) != 0)) != 0 ? option.palette.text() 0604 : option.palette.highlight()); 0605 progress.setWidth((thumbRect.width() - 2) * jobProgress / 100); 0606 painter->drawRoundedRect(progress, 2, 2); 0607 } else { 0608 // Draw kind of a pause icon 0609 progress.setWidth(3); 0610 painter->drawRect(progress); 0611 progress.moveLeft(progress.right() + 3); 0612 painter->drawRect(progress); 0613 } 0614 } 0615 bool jobsucceeded = index.data(AbstractProjectItem::JobSuccess).toBool(); 0616 if (!jobsucceeded) { 0617 QIcon warning = QIcon::fromTheme(QStringLiteral("process-stop")); 0618 QRect thumbRect = m_thumbRect.adjusted(2, 2, 2, 2); 0619 warning.paint(painter, thumbRect); 0620 } 0621 } 0622 } 0623 0624 int getFrame(const QModelIndex &index, QPoint pos) 0625 { 0626 int type = index.data(AbstractProjectItem::ItemTypeRole).toInt(); 0627 if ((type != AbstractProjectItem::ClipItem && type != AbstractProjectItem::SubClipItem) || !m_thumbRect.contains(pos)) { 0628 return 0; 0629 } 0630 return 100 * (pos.x() - m_thumbRect.x()) / m_thumbRect.width(); 0631 } 0632 0633 QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &) const override 0634 { 0635 int textHeight = int(option.fontMetrics.height() * 1.5); 0636 return (QSize(m_iconSize.width(), m_iconSize.height() + textHeight)); 0637 } 0638 0639 private: 0640 mutable bool m_editorOpen{false}; 0641 mutable QRect m_audioDragRect; 0642 mutable QRect m_videoDragRect; 0643 mutable QRect m_thumbRect; 0644 0645 public: 0646 PlaylistState::ClipState dragType{PlaylistState::Disabled}; 0647 }; 0648 0649 MyListView::MyListView(QWidget *parent) 0650 : QListView(parent) 0651 { 0652 setViewMode(QListView::IconMode); 0653 setMovement(QListView::Static); 0654 setResizeMode(QListView::Adjust); 0655 setWordWrap(true); 0656 setDragDropMode(QAbstractItemView::DragDrop); 0657 setUniformItemSizes(true); 0658 setDragEnabled(true); 0659 setAcceptDrops(true); 0660 // setDropIndicatorShown(true); 0661 viewport()->setAcceptDrops(true); 0662 } 0663 0664 void MyListView::focusInEvent(QFocusEvent *event) 0665 { 0666 QListView::focusInEvent(event); 0667 if (event->reason() == Qt::MouseFocusReason) { 0668 Q_EMIT focusView(); 0669 } 0670 } 0671 0672 void MyListView::dropEvent(QDropEvent *event) 0673 { 0674 if (event->mimeData()->hasFormat(QStringLiteral("text/producerslist"))) { 0675 // Internal drag/drop, ensure it is not a zone drop 0676 if (!QString(event->mimeData()->data(QStringLiteral("text/producerslist"))).contains(QLatin1Char('/'))) { 0677 bool isSameRoot = false; 0678 QString rootId = QString(event->mimeData()->data(QStringLiteral("text/rootId"))); 0679 if (rootIndex().data(AbstractProjectItem::DataId).toString() == rootId) { 0680 isSameRoot = true; 0681 } 0682 #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) 0683 if (isSameRoot && !indexAt(event->position().toPoint()).isValid()) { 0684 #else 0685 if (isSameRoot && !indexAt(event->pos()).isValid()) { 0686 #endif 0687 event->ignore(); 0688 return; 0689 } 0690 } 0691 } 0692 QListView::dropEvent(event); 0693 } 0694 0695 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0696 void MyListView::enterEvent(QEnterEvent *event) 0697 #else 0698 void MyListView::enterEvent(QEvent *event) 0699 #endif 0700 { 0701 QListView::enterEvent(event); 0702 pCore->setWidgetKeyBinding(i18n("<b>Double click</b> to add a file to the project")); 0703 } 0704 0705 void MyListView::leaveEvent(QEvent *event) 0706 { 0707 QListView::leaveEvent(event); 0708 pCore->setWidgetKeyBinding(); 0709 } 0710 0711 void MyListView::mousePressEvent(QMouseEvent *event) 0712 { 0713 if (event->button() == Qt::LeftButton) { 0714 QModelIndex ix = indexAt(event->pos()); 0715 if (ix.isValid()) { 0716 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0717 QAbstractItemDelegate *del = itemDelegate(ix); 0718 #else 0719 QAbstractItemDelegate *del = itemDelegateForIndex(ix); 0720 #endif 0721 m_dragType = static_cast<BinListItemDelegate *>(del)->dragType; 0722 m_startPos = event->pos(); 0723 } else { 0724 m_dragType = PlaylistState::Disabled; 0725 m_startPos = QPoint(); 0726 } 0727 Q_EMIT updateDragMode(m_dragType); 0728 } 0729 QListView::mousePressEvent(event); 0730 } 0731 0732 void MyListView::mouseReleaseEvent(QMouseEvent *event) 0733 { 0734 m_startPos = QPoint(); 0735 QListView::mouseReleaseEvent(event); 0736 } 0737 0738 void MyListView::mouseMoveEvent(QMouseEvent *event) 0739 { 0740 if ((event->buttons() & Qt::LeftButton) != 0u) { 0741 if (!m_startPos.isNull() && (event->pos() - m_startPos).manhattanLength() > QApplication::startDragDistance()) { 0742 QModelIndexList indexes = selectedIndexes(); 0743 if (indexes.isEmpty()) { 0744 // Dragging from empty zone, abort 0745 QListView::mouseMoveEvent(event); 0746 return; 0747 } 0748 auto *drag = new QDrag(this); 0749 drag->setMimeData(model()->mimeData(indexes)); 0750 QModelIndex ix = indexes.constFirst(); 0751 if (ix.isValid()) { 0752 QIcon icon = ix.data(AbstractProjectItem::DataThumbnail).value<QIcon>(); 0753 QPixmap pix = icon.pixmap(iconSize()); 0754 QSize size = pix.size() / 2; 0755 QImage image(size, QImage::Format_ARGB32_Premultiplied); 0756 image.fill(Qt::transparent); 0757 QPainter p(&image); 0758 p.setOpacity(0.7); 0759 p.drawPixmap(0, 0, image.width(), image.height(), pix); 0760 p.setOpacity(1); 0761 if (indexes.count() > 1) { 0762 QPalette palette; 0763 int radius = size.height() / 3; 0764 p.setBrush(palette.highlight()); 0765 p.setPen(palette.highlightedText().color()); 0766 p.drawEllipse(QPoint(size.width() / 2, size.height() / 2), radius, radius); 0767 p.drawText(size.width() / 2 - radius, size.height() / 2 - radius, 2 * radius, 2 * radius, Qt::AlignCenter, 0768 QString::number(indexes.count())); 0769 } 0770 p.end(); 0771 drag->setPixmap(QPixmap::fromImage(image)); 0772 } 0773 drag->exec(); 0774 Q_EMIT processDragEnd(); 0775 return; 0776 } 0777 QListView::mouseMoveEvent(event); 0778 return; 0779 } 0780 QModelIndex index = indexAt(event->pos()); 0781 if (index.isValid()) { 0782 if (KdenliveSettings::hoverPreview()) { 0783 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0784 QAbstractItemDelegate *del = itemDelegate(index); 0785 #else 0786 QAbstractItemDelegate *del = itemDelegateForIndex(index); 0787 #endif 0788 if (del) { 0789 auto delegate = static_cast<BinListItemDelegate *>(del); 0790 QRect vRect = visualRect(index); 0791 if (vRect.contains(event->pos())) { 0792 if (m_lastHoveredItem != index) { 0793 if (m_lastHoveredItem.isValid()) { 0794 Q_EMIT displayBinFrame(m_lastHoveredItem, -1); 0795 } 0796 m_lastHoveredItem = index; 0797 } 0798 int frame = delegate->getFrame(index, event->pos()); 0799 Q_EMIT displayBinFrame(index, frame, event->modifiers() & Qt::ShiftModifier); 0800 } else if (m_lastHoveredItem == index) { 0801 Q_EMIT displayBinFrame(m_lastHoveredItem, -1); 0802 m_lastHoveredItem = QModelIndex(); 0803 } 0804 } else { 0805 if (m_lastHoveredItem.isValid()) { 0806 Q_EMIT displayBinFrame(m_lastHoveredItem, -1); 0807 m_lastHoveredItem = QModelIndex(); 0808 } 0809 } 0810 pCore->bin()->updateKeyBinding(i18n("<b>Shift+seek</b> over thumbnail to set default thumbnail, <b>F2</b> to rename selected item")); 0811 } else { 0812 pCore->bin()->updateKeyBinding(i18n("<b>F2</b> to rename selected item")); 0813 } 0814 } else { 0815 pCore->bin()->updateKeyBinding(); 0816 if (m_lastHoveredItem.isValid()) { 0817 Q_EMIT displayBinFrame(m_lastHoveredItem, -1); 0818 m_lastHoveredItem = QModelIndex(); 0819 } 0820 } 0821 if (m_startPos.isNull() && event->buttons() == Qt::NoButton) { 0822 QListView::mouseMoveEvent(event); 0823 } 0824 } 0825 0826 MyTreeView::MyTreeView(QWidget *parent) 0827 : QTreeView(parent) 0828 { 0829 setEditing(false); 0830 setAcceptDrops(true); 0831 } 0832 0833 void MyTreeView::mousePressEvent(QMouseEvent *event) 0834 { 0835 QTreeView::mousePressEvent(event); 0836 if (event->button() == Qt::LeftButton) { 0837 QModelIndex ix = indexAt(event->pos()); 0838 if (ix.isValid()) { 0839 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0840 QAbstractItemDelegate *del = itemDelegate(ix); 0841 #else 0842 QAbstractItemDelegate *del = itemDelegateForIndex(ix); 0843 #endif 0844 m_dragType = static_cast<BinItemDelegate *>(del)->dragType; 0845 m_startPos = event->pos(); 0846 0847 } else { 0848 m_dragType = PlaylistState::Disabled; 0849 m_startPos = QPoint(); 0850 } 0851 } 0852 event->accept(); 0853 } 0854 0855 void MyTreeView::mouseReleaseEvent(QMouseEvent *event) 0856 { 0857 m_startPos = QPoint(); 0858 QTreeView::mouseReleaseEvent(event); 0859 } 0860 0861 void MyTreeView::focusInEvent(QFocusEvent *event) 0862 { 0863 QTreeView::focusInEvent(event); 0864 if (event->reason() == Qt::MouseFocusReason) { 0865 Q_EMIT focusView(); 0866 } 0867 } 0868 0869 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0870 void MyTreeView::enterEvent(QEnterEvent *event) 0871 #else 0872 void MyTreeView::enterEvent(QEvent *event) 0873 #endif 0874 { 0875 QTreeView::enterEvent(event); 0876 pCore->setWidgetKeyBinding(i18n("<b>Double click</b> to add a file to the project")); 0877 } 0878 0879 void MyTreeView::leaveEvent(QEvent *event) 0880 { 0881 QTreeView::leaveEvent(event); 0882 pCore->setWidgetKeyBinding(); 0883 } 0884 0885 void MyTreeView::mouseMoveEvent(QMouseEvent *event) 0886 { 0887 if ((event->buttons() & Qt::LeftButton) != 0u) { 0888 if (!m_startPos.isNull()) { 0889 int distance = (event->pos() - m_startPos).manhattanLength(); 0890 if (distance >= QApplication::startDragDistance()) { 0891 performDrag(); 0892 return; 0893 } 0894 } 0895 QTreeView::mouseMoveEvent(event); 0896 return; 0897 } else { 0898 QModelIndex index = indexAt(event->pos()); 0899 if (index.isValid()) { 0900 if (KdenliveSettings::hoverPreview()) { 0901 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0902 QAbstractItemDelegate *del = itemDelegate(index); 0903 #else 0904 QAbstractItemDelegate *del = itemDelegateForIndex(index); 0905 #endif 0906 int frame = static_cast<BinItemDelegate *>(del)->getFrame(index, event->pos().x()); 0907 if (frame >= 0) { 0908 Q_EMIT displayBinFrame(index, frame, event->modifiers() & Qt::ShiftModifier); 0909 if (m_lastHoveredItem != index) { 0910 if (m_lastHoveredItem.isValid()) { 0911 Q_EMIT displayBinFrame(m_lastHoveredItem, -1); 0912 } 0913 m_lastHoveredItem = index; 0914 } 0915 } else if (m_lastHoveredItem.isValid()) { 0916 Q_EMIT displayBinFrame(m_lastHoveredItem, -1); 0917 m_lastHoveredItem = QModelIndex(); 0918 } 0919 pCore->bin()->updateKeyBinding(i18n("<b>Shift+seek</b> over thumbnail to set default thumbnail, <b>F2</b> to rename selected item")); 0920 } else { 0921 pCore->bin()->updateKeyBinding(i18n("<b>F2</b> to rename selected item")); 0922 } 0923 } else { 0924 if (m_lastHoveredItem.isValid()) { 0925 Q_EMIT displayBinFrame(m_lastHoveredItem, -1); 0926 m_lastHoveredItem = QModelIndex(); 0927 } 0928 pCore->bin()->updateKeyBinding(); 0929 } 0930 } 0931 if (m_startPos.isNull() && event->buttons() == Qt::NoButton) { 0932 QTreeView::mouseMoveEvent(event); 0933 } 0934 } 0935 0936 void MyTreeView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) 0937 { 0938 QAbstractItemView::closeEditor(editor, hint); 0939 setEditing(false); 0940 } 0941 0942 void MyTreeView::editorDestroyed(QObject *editor) 0943 { 0944 QAbstractItemView::editorDestroyed(editor); 0945 setEditing(false); 0946 } 0947 0948 bool MyTreeView::isEditing() const 0949 { 0950 return state() == QAbstractItemView::EditingState; 0951 } 0952 0953 void MyTreeView::setEditing(bool edit) 0954 { 0955 setState(edit ? QAbstractItemView::EditingState : QAbstractItemView::NoState); 0956 if (!edit) { 0957 // Ensure edited item is selected 0958 Q_EMIT selectCurrent(); 0959 } 0960 } 0961 0962 bool MyTreeView::performDrag() 0963 { 0964 QModelIndexList bases = selectedIndexes(); 0965 QModelIndexList indexes; 0966 for (int i = 0; i < bases.count(); i++) { 0967 if (bases.at(i).column() == 0) { 0968 indexes << bases.at(i); 0969 } 0970 } 0971 if (indexes.isEmpty()) { 0972 return false; 0973 } 0974 // Check if we want audio or video only 0975 Q_EMIT updateDragMode(m_dragType); 0976 auto *drag = new QDrag(this); 0977 drag->setMimeData(model()->mimeData(indexes)); 0978 QModelIndex ix = indexes.constFirst(); 0979 if (ix.isValid()) { 0980 QIcon icon = ix.data(AbstractProjectItem::DataThumbnail).value<QIcon>(); 0981 QPixmap pix = icon.pixmap(iconSize()); 0982 QSize size = pix.size() / 2; 0983 QImage image(size, QImage::Format_ARGB32_Premultiplied); 0984 image.fill(Qt::transparent); 0985 QPainter p(&image); 0986 p.setOpacity(0.7); 0987 p.drawPixmap(0, 0, image.width(), image.height(), pix); 0988 p.setOpacity(1); 0989 if (indexes.count() > 1) { 0990 QPalette palette; 0991 int radius = size.height() / 3; 0992 p.setBrush(palette.highlight()); 0993 p.setPen(palette.highlightedText().color()); 0994 p.drawEllipse(QPoint(size.width() / 2, size.height() / 2), radius, radius); 0995 p.drawText(size.width() / 2 - radius, size.height() / 2 - radius, 2 * radius, 2 * radius, Qt::AlignCenter, QString::number(indexes.count())); 0996 } 0997 p.end(); 0998 drag->setPixmap(QPixmap::fromImage(image)); 0999 } 1000 drag->exec(); 1001 drag->deleteLater(); 1002 Q_EMIT processDragEnd(); 1003 return true; 1004 } 1005 1006 SmallJobLabel::SmallJobLabel(QWidget *parent) 1007 : QPushButton(parent) 1008 1009 { 1010 setFixedWidth(0); 1011 setFlat(true); 1012 m_timeLine = new QTimeLine(500, this); 1013 QObject::connect(m_timeLine, &QTimeLine::valueChanged, this, &SmallJobLabel::slotTimeLineChanged); 1014 QObject::connect(m_timeLine, &QTimeLine::finished, this, &SmallJobLabel::slotTimeLineFinished); 1015 hide(); 1016 } 1017 1018 const QString SmallJobLabel::getStyleSheet(const QPalette &p) 1019 { 1020 KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window); 1021 QColor bg = scheme.background(KColorScheme::LinkBackground).color(); 1022 QColor fg = scheme.foreground(KColorScheme::LinkText).color(); 1023 QString style = 1024 QStringLiteral("QPushButton {margin:3px;padding:2px;background-color: rgb(%1, %2, %3);border-radius: 4px;border: none;color: rgb(%4, %5, %6)}") 1025 .arg(bg.red()) 1026 .arg(bg.green()) 1027 .arg(bg.blue()) 1028 .arg(fg.red()) 1029 .arg(fg.green()) 1030 .arg(fg.blue()); 1031 1032 bg = scheme.background(KColorScheme::ActiveBackground).color(); 1033 fg = scheme.foreground(KColorScheme::ActiveText).color(); 1034 style.append( 1035 QStringLiteral("\nQPushButton:hover {margin:3px;padding:2px;background-color: rgb(%1, %2, %3);border-radius: 4px;border: none;color: rgb(%4, %5, %6)}") 1036 .arg(bg.red()) 1037 .arg(bg.green()) 1038 .arg(bg.blue()) 1039 .arg(fg.red()) 1040 .arg(fg.green()) 1041 .arg(fg.blue())); 1042 1043 return style; 1044 } 1045 1046 void SmallJobLabel::setAction(QAction *action) 1047 { 1048 m_action = action; 1049 } 1050 1051 void SmallJobLabel::slotTimeLineChanged(qreal value) 1052 { 1053 setFixedWidth(int(qMin(value * 2, qreal(1.0)) * sizeHint().width())); 1054 update(); 1055 } 1056 1057 void SmallJobLabel::slotTimeLineFinished() 1058 { 1059 if (m_timeLine->direction() == QTimeLine::Forward) { 1060 // Show 1061 m_action->setVisible(true); 1062 } else { 1063 // Hide 1064 m_action->setVisible(false); 1065 setText(QString()); 1066 } 1067 } 1068 1069 void SmallJobLabel::slotSetJobCount(int jobCount) 1070 { 1071 QMutexLocker lk(&m_locker); 1072 if (jobCount > 0) { 1073 // prepare animation 1074 setText(i18np("%1 job", "%1 jobs", jobCount)); 1075 setToolTip(i18np("%1 pending job", "%1 pending jobs", jobCount)); 1076 1077 if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this) != 0) { 1078 setFixedWidth(sizeHint().width()); 1079 m_action->setVisible(true); 1080 return; 1081 } 1082 1083 if (m_action->isVisible()) { 1084 setFixedWidth(sizeHint().width()); 1085 update(); 1086 return; 1087 } 1088 1089 setFixedWidth(0); 1090 m_action->setVisible(true); 1091 int wantedWidth = sizeHint().width(); 1092 setGeometry(-wantedWidth, 0, wantedWidth, height()); 1093 m_timeLine->setDirection(QTimeLine::Forward); 1094 if (m_timeLine->state() == QTimeLine::NotRunning) { 1095 m_timeLine->start(); 1096 } 1097 } else { 1098 if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this) != 0) { 1099 setFixedWidth(0); 1100 m_action->setVisible(false); 1101 return; 1102 } 1103 // hide 1104 m_timeLine->setDirection(QTimeLine::Backward); 1105 if (m_timeLine->state() == QTimeLine::NotRunning) { 1106 m_timeLine->start(); 1107 } 1108 } 1109 } 1110 1111 LineEventEater::LineEventEater(QObject *parent) 1112 : QObject(parent) 1113 { 1114 } 1115 1116 bool LineEventEater::eventFilter(QObject *obj, QEvent *event) 1117 { 1118 switch (event->type()) { 1119 case QEvent::ShortcutOverride: 1120 if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Escape) { 1121 Q_EMIT clearSearchLine(); 1122 } 1123 break; 1124 case QEvent::Resize: 1125 // Workaround Qt BUG 54676 1126 Q_EMIT showClearButton(static_cast<QResizeEvent *>(event)->size().width() > QFontMetrics(QApplication::font()).averageCharWidth() * 8); 1127 break; 1128 default: 1129 break; 1130 } 1131 return QObject::eventFilter(obj, event); 1132 } 1133 1134 Bin::Bin(std::shared_ptr<ProjectItemModel> model, QWidget *parent, bool isMainBin) 1135 : QWidget(parent) 1136 , isLoading(false) 1137 , shouldCheckProfile(false) 1138 , m_isMainBin(isMainBin) 1139 , m_itemModel(std::move(model)) 1140 , m_itemView(nullptr) 1141 , m_binTreeViewDelegate(nullptr) 1142 , m_binListViewDelegate(nullptr) 1143 , m_doc(nullptr) 1144 , m_extractAudioAction(nullptr) 1145 , m_transcodeAction(nullptr) 1146 , m_clipsActionsMenu(nullptr) 1147 , m_inTimelineAction(nullptr) 1148 , m_listType(BinViewType(KdenliveSettings::binMode())) 1149 , m_baseIconSize(160, 90) 1150 , m_propertiesDock(nullptr) 1151 , m_propertiesPanel(nullptr) 1152 , m_monitor(nullptr) 1153 , m_blankThumb() 1154 , m_filterTagGroup(this) 1155 , m_filterRateGroup(this) 1156 , m_filterUsageGroup(this) 1157 , m_filterTypeGroup(this) 1158 , m_invalidClipDialog(nullptr) 1159 , m_transcodingDialog(nullptr) 1160 , m_gainedFocus(false) 1161 , m_audioDuration(0) 1162 , m_processedAudio(0) 1163 { 1164 m_layout = new QVBoxLayout(this); 1165 1166 // Create toolbar for buttons 1167 m_toolbar = new QToolBar(this); 1168 int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize); 1169 m_toolbar->setIconSize(QSize(iconSize, iconSize)); 1170 m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); 1171 m_layout->addWidget(m_toolbar); 1172 1173 if (m_isMainBin) { 1174 // Init icons 1175 m_audioIcon = QImage(iconSize, iconSize, QImage::Format_ARGB32_Premultiplied); 1176 m_videoIcon = QImage(iconSize, iconSize, QImage::Format_ARGB32_Premultiplied); 1177 m_folderIcon = QIcon::fromTheme(QStringLiteral("folder")); 1178 m_sequenceFolderIcon = QIcon::fromTheme(QStringLiteral("folder-yellow")); 1179 } 1180 1181 // Tags panel 1182 m_tagsWidget = new TagWidget(this); 1183 connect(m_tagsWidget, &TagWidget::switchTag, this, &Bin::switchTag); 1184 connect(m_tagsWidget, &TagWidget::updateProjectTags, this, &Bin::updateTags); 1185 m_tagsWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); 1186 m_layout->addWidget(m_tagsWidget); 1187 m_tagsWidget->setVisible(false); 1188 1189 m_layout->setSpacing(0); 1190 m_layout->setContentsMargins(0, 0, 0, 0); 1191 // Search line 1192 m_searchLine = new QLineEdit(this); 1193 m_searchLine->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); 1194 m_searchLine->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 1195 // m_searchLine->setClearButtonEnabled(true); 1196 m_searchLine->setPlaceholderText(i18n("Search…")); 1197 m_searchLine->setFocusPolicy(Qt::ClickFocus); 1198 m_searchLine->setAccessibleName(i18n("Bin Search")); 1199 connect(m_searchLine, &QLineEdit::textChanged, this, [this](const QString &str) { 1200 m_proxyModel->slotSetSearchString(str); 1201 if (str.isEmpty()) { 1202 // focus last selected item when clearing search line 1203 QModelIndex current = m_proxyModel->selectionModel()->currentIndex(); 1204 if (current.isValid()) { 1205 m_itemView->scrollTo(current, QAbstractItemView::EnsureVisible); 1206 } 1207 } 1208 }); 1209 1210 auto *leventEater = new LineEventEater(this); 1211 m_searchLine->installEventFilter(leventEater); 1212 connect(leventEater, &LineEventEater::clearSearchLine, m_searchLine, &QLineEdit::clear); 1213 connect(leventEater, &LineEventEater::showClearButton, this, &Bin::showClearButton); 1214 1215 setFocusPolicy(Qt::ClickFocus); 1216 connect(this, &Bin::refreshPanel, this, &Bin::doRefreshPanel); 1217 1218 // Zoom slider 1219 QWidget *container = new QWidget(this); 1220 auto *lay = new QHBoxLayout; 1221 m_slider = new QSlider(Qt::Horizontal, this); 1222 m_slider->setMinimumWidth(40); 1223 m_slider->setRange(0, 10); 1224 m_slider->setValue(KdenliveSettings::bin_zoom()); 1225 connect(m_slider, &QAbstractSlider::valueChanged, this, &Bin::slotSetIconSize); 1226 auto *tb1 = new QToolButton(this); 1227 tb1->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); 1228 connect(tb1, &QToolButton::clicked, this, [&]() { m_slider->setValue(qMin(m_slider->value() + 1, m_slider->maximum())); }); 1229 auto *tb2 = new QToolButton(this); 1230 tb2->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); 1231 connect(tb2, &QToolButton::clicked, this, [&]() { m_slider->setValue(qMax(m_slider->value() - 1, m_slider->minimum())); }); 1232 lay->addWidget(tb2); 1233 lay->addWidget(m_slider); 1234 lay->addWidget(tb1); 1235 container->setLayout(lay); 1236 auto *zoomWidget = new QWidgetAction(this); 1237 zoomWidget->setDefaultWidget(container); 1238 1239 // View type 1240 KSelectAction *listType = new KSelectAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18n("View Mode"), this); 1241 pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode"), listType); 1242 pCore->window()->actionCollection()->setShortcutsConfigurable(listType, false); 1243 QAction *treeViewAction = listType->addAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18n("Tree View")); 1244 listType->addAction(treeViewAction); 1245 treeViewAction->setData(BinTreeView); 1246 if (m_listType == treeViewAction->data().toInt()) { 1247 listType->setCurrentAction(treeViewAction); 1248 } 1249 pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode_tree"), treeViewAction); 1250 QAction *profileAction = new QAction(i18n("Adjust Profile to Current Clip"), this); 1251 connect(profileAction, &QAction::triggered, this, &Bin::adjustProjectProfileToItem); 1252 pCore->window()->actionCollection()->addAction(QStringLiteral("project_adjust_profile"), profileAction); 1253 1254 QAction *iconViewAction = listType->addAction(QIcon::fromTheme(QStringLiteral("view-list-icons")), i18n("Icon View")); 1255 iconViewAction->setData(BinIconView); 1256 if (m_listType == iconViewAction->data().toInt()) { 1257 listType->setCurrentAction(iconViewAction); 1258 } 1259 pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode_icon"), iconViewAction); 1260 1261 // Sort menu 1262 m_sortDescend = new QAction(i18n("Descending"), this); 1263 m_sortDescend->setCheckable(true); 1264 m_sortDescend->setChecked(KdenliveSettings::binSorting() > 99); 1265 connect(m_sortDescend, &QAction::triggered, this, [&]() { 1266 if (m_sortGroup->checkedAction()) { 1267 int actionData = m_sortGroup->checkedAction()->data().toInt(); 1268 if ((m_itemView != nullptr) && m_listType == BinTreeView) { 1269 auto *view = static_cast<QTreeView *>(m_itemView); 1270 view->header()->setSortIndicator(actionData, m_sortDescend->isChecked() ? Qt::DescendingOrder : Qt::AscendingOrder); 1271 } else { 1272 m_proxyModel->sort(actionData, m_sortDescend->isChecked() ? Qt::DescendingOrder : Qt::AscendingOrder); 1273 } 1274 KdenliveSettings::setBinSorting(actionData + (m_sortDescend->isChecked() ? 100 : 0)); 1275 } 1276 }); 1277 1278 auto *sortAction = new KActionMenu(i18n("Sort By"), this); 1279 int binSort = KdenliveSettings::binSorting() % 100; 1280 m_sortGroup = new QActionGroup(sortAction); 1281 QAction *sortByName = new QAction(i18n("Name"), m_sortGroup); 1282 sortByName->setCheckable(true); 1283 sortByName->setData(0); 1284 sortByName->setChecked(binSort == 0); 1285 QAction *sortByDate = new QAction(i18n("Date"), m_sortGroup); 1286 sortByDate->setCheckable(true); 1287 sortByDate->setData(1); 1288 sortByDate->setChecked(binSort == 1); 1289 QAction *sortByDesc = new QAction(i18n("Description"), m_sortGroup); 1290 sortByDesc->setCheckable(true); 1291 sortByDesc->setData(2); 1292 sortByDesc->setChecked(binSort == 2); 1293 QAction *sortByType = new QAction(i18n("Type"), m_sortGroup); 1294 sortByType->setCheckable(true); 1295 sortByType->setData(3); 1296 sortByType->setChecked(binSort == 3); 1297 QAction *sortByDuration = new QAction(i18n("Duration"), m_sortGroup); 1298 sortByDuration->setCheckable(true); 1299 sortByDuration->setData(5); 1300 sortByDuration->setChecked(binSort == 5); 1301 QAction *sortByInsert = new QAction(i18n("Insert Order"), m_sortGroup); 1302 sortByInsert->setCheckable(true); 1303 sortByInsert->setData(6); 1304 sortByInsert->setChecked(binSort == 6); 1305 QAction *sortByRating = new QAction(i18n("Rating"), m_sortGroup); 1306 sortByRating->setCheckable(true); 1307 sortByRating->setData(7); 1308 sortByRating->setChecked(binSort == 7); 1309 sortAction->addAction(sortByName); 1310 sortAction->addAction(sortByDate); 1311 sortAction->addAction(sortByDuration); 1312 sortAction->addAction(sortByRating); 1313 sortAction->addAction(sortByType); 1314 sortAction->addAction(sortByInsert); 1315 sortAction->addAction(sortByDesc); 1316 sortAction->addSeparator(); 1317 sortAction->addAction(m_sortDescend); 1318 connect(m_sortGroup, &QActionGroup::triggered, this, [&](QAction *ac) { 1319 int actionData = ac->data().toInt(); 1320 if ((m_itemView != nullptr) && m_listType == BinTreeView) { 1321 auto *view = static_cast<QTreeView *>(m_itemView); 1322 view->header()->setSortIndicator(actionData, m_sortDescend->isChecked() ? Qt::DescendingOrder : Qt::AscendingOrder); 1323 } else { 1324 m_proxyModel->sort(actionData, m_sortDescend->isChecked() ? Qt::DescendingOrder : Qt::AscendingOrder); 1325 } 1326 KdenliveSettings::setBinSorting(actionData + (m_sortDescend->isChecked() ? 100 : 0)); 1327 }); 1328 1329 QAction *disableEffects = new QAction(i18n("Disable Bin Effects"), this); 1330 disableEffects->setIcon(QIcon::fromTheme(QStringLiteral("favorite"))); 1331 disableEffects->setData("disable_bin_effects"); 1332 disableEffects->setCheckable(true); 1333 disableEffects->setChecked(false); 1334 connect(disableEffects, &QAction::triggered, this, [this](bool disable) { this->setBinEffectsEnabled(!disable); }); 1335 pCore->window()->actionCollection()->addAction(QStringLiteral("disable_bin_effects"), disableEffects); 1336 1337 QAction *hoverPreview = new QAction(i18n("Show Video Preview in Thumbnails"), this); 1338 hoverPreview->setCheckable(true); 1339 hoverPreview->setChecked(KdenliveSettings::hoverPreview()); 1340 connect(hoverPreview, &QAction::triggered, [](bool checked) { KdenliveSettings::setHoverPreview(checked); }); 1341 1342 listType->setToolBarMode(KSelectAction::MenuMode); 1343 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 240, 0) 1344 connect(listType, &KSelectAction::actionTriggered, this, &Bin::slotInitView); 1345 #else 1346 connect(listType, static_cast<void (KSelectAction::*)(QAction *)>(&KSelectAction::triggered), this, &Bin::slotInitView); 1347 #endif 1348 1349 // Settings menu 1350 auto *settingsAction = new KActionMenu(QIcon::fromTheme(QStringLiteral("application-menu")), i18n("Options"), this); 1351 settingsAction->setWhatsThis(xi18nc("@info:whatsthis", "Opens a window to configure the project bin (e.g. view mode, sort, show rating).")); 1352 settingsAction->setPopupMode(QToolButton::InstantPopup); 1353 settingsAction->addAction(zoomWidget); 1354 settingsAction->addAction(listType); 1355 settingsAction->addAction(sortAction); 1356 1357 // Column show / hide actions 1358 m_showDate = new QAction(i18n("Show Date"), this); 1359 m_showDate->setCheckable(true); 1360 m_showDate->setData(1); 1361 connect(m_showDate, &QAction::triggered, this, &Bin::slotShowColumn); 1362 m_showDesc = new QAction(i18n("Show Description"), this); 1363 m_showDesc->setCheckable(true); 1364 m_showDesc->setData(2); 1365 connect(m_showDesc, &QAction::triggered, this, &Bin::slotShowColumn); 1366 m_showRating = new QAction(i18n("Show Rating"), this); 1367 m_showRating->setCheckable(true); 1368 m_showRating->setData(7); 1369 connect(m_showRating, &QAction::triggered, this, &Bin::slotShowColumn); 1370 1371 settingsAction->addAction(m_showDate); 1372 settingsAction->addAction(m_showDesc); 1373 settingsAction->addAction(m_showRating); 1374 settingsAction->addAction(disableEffects); 1375 settingsAction->addAction(hoverPreview); 1376 1377 if (!m_isMainBin) { 1378 // Add close action 1379 QAction *close = KStandardAction::close(this, SIGNAL(requestBinClose()), this); 1380 settingsAction->addAction(close); 1381 } 1382 1383 // Show tags panel 1384 m_tagAction = new QAction(QIcon::fromTheme(QStringLiteral("tag")), i18n("Tags Panel"), this); 1385 m_tagAction->setCheckable(true); 1386 m_toolbar->addAction(m_tagAction); 1387 connect(m_tagAction, &QAction::triggered, this, [&](bool triggered) { 1388 if (triggered) { 1389 m_tagsWidget->setVisible(true); 1390 } else { 1391 m_tagsWidget->setVisible(false); 1392 } 1393 }); 1394 1395 // Filter menu 1396 m_filterTagGroup.setExclusive(false); 1397 m_filterRateGroup.setExclusive(false); 1398 m_filterUsageGroup.setExclusive(true); 1399 m_filterTypeGroup.setExclusive(false); 1400 m_filterMenu = new QMenu(i18n("Filter"), this); 1401 m_filterButton = new QToolButton; 1402 m_filterButton->setCheckable(true); 1403 m_filterButton->setPopupMode(QToolButton::MenuButtonPopup); 1404 m_filterButton->setIcon(QIcon::fromTheme(QStringLiteral("view-filter"))); 1405 m_filterButton->setToolTip(i18n("Filter")); 1406 m_filterButton->setWhatsThis(xi18nc("@info:whatsthis", "Filter the project bin contents. Click on the filter icon to toggle the filter display. Click on " 1407 "the arrow icon to open a list of possible filter settings.")); 1408 m_filterButton->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 1409 m_filterButton->setMenu(m_filterMenu); 1410 1411 connect(m_filterButton, &QToolButton::toggled, this, [this](bool toggle) { 1412 if (!toggle) { 1413 m_proxyModel->slotClearSearchFilters(); 1414 return; 1415 } 1416 slotApplyFilters(); 1417 }); 1418 1419 connect(m_filterMenu, &QMenu::triggered, this, [this](QAction *action) { 1420 if (action->data().toString().isEmpty()) { 1421 // Clear filters action 1422 QSignalBlocker bk(m_filterMenu); 1423 QList<QAction *> list = m_filterMenu->actions(); 1424 list << m_filterTypeGroup.actions(); 1425 for (QAction *ac : qAsConst(list)) { 1426 ac->setChecked(false); 1427 } 1428 m_proxyModel->slotClearSearchFilters(); 1429 m_filterButton->setChecked(false); 1430 return; 1431 } 1432 slotApplyFilters(); 1433 }); 1434 1435 m_tagAction->setCheckable(true); 1436 m_toolbar->addAction(m_tagAction); 1437 m_toolbar->addAction(settingsAction); 1438 1439 if (m_isMainBin) { 1440 // small info button for pending jobs 1441 m_infoLabel = new SmallJobLabel(this); 1442 m_infoLabel->setStyleSheet(SmallJobLabel::getStyleSheet(palette())); 1443 connect(&pCore->taskManager, &TaskManager::jobCount, m_infoLabel, &SmallJobLabel::slotSetJobCount); 1444 QAction *infoAction = m_toolbar->addWidget(m_infoLabel); 1445 m_jobsMenu = new QMenu(this); 1446 // connect(m_jobsMenu, &QMenu::aboutToShow, this, &Bin::slotPrepareJobsMenu); 1447 m_cancelJobs = new QAction(i18n("Cancel All Jobs"), this); 1448 m_cancelJobs->setCheckable(false); 1449 m_discardCurrentClipJobs = new QAction(i18n("Cancel Current Clip Jobs"), this); 1450 m_discardCurrentClipJobs->setCheckable(false); 1451 m_discardPendingJobs = new QAction(i18n("Cancel Pending Jobs"), this); 1452 m_discardPendingJobs->setCheckable(false); 1453 m_jobsMenu->addAction(m_cancelJobs); 1454 m_jobsMenu->addAction(m_discardCurrentClipJobs); 1455 m_jobsMenu->addAction(m_discardPendingJobs); 1456 m_infoLabel->setMenu(m_jobsMenu); 1457 m_infoLabel->setAction(infoAction); 1458 1459 connect(m_discardCurrentClipJobs, &QAction::triggered, this, [&]() { 1460 const QString currentId = m_monitor->activeClipId(); 1461 if (!currentId.isEmpty()) { 1462 pCore->taskManager.discardJobs(ObjectId(KdenliveObjectType::BinClip, currentId.toInt(), QUuid()), AbstractTask::NOJOBTYPE, true); 1463 } 1464 }); 1465 connect(m_cancelJobs, &QAction::triggered, [&]() { pCore->taskManager.slotCancelJobs(); }); 1466 connect(m_discardPendingJobs, &QAction::triggered, [&]() { 1467 // TODO: implement pending only deletion 1468 pCore->taskManager.slotCancelJobs(); 1469 }); 1470 } 1471 // Hack, create toolbar spacer 1472 QWidget *spacer = new QWidget(); 1473 spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 1474 m_toolbar->addWidget(spacer); 1475 1476 // Add filter and search line 1477 m_toolbar->addWidget(m_filterButton); 1478 m_toolbar->addWidget(m_searchLine); 1479 1480 // connect(pCore->projectManager(), SIGNAL(projectOpened(Project*)), this, SLOT(setProject(Project*))); 1481 m_headerInfo = QByteArray::fromBase64(KdenliveSettings::treeviewheaders().toLatin1()); 1482 if (m_isMainBin) { 1483 m_propertiesPanel = new QScrollArea(this); 1484 m_propertiesPanel->setFrameShape(QFrame::NoFrame); 1485 m_propertiesPanel->setAccessibleName(i18n("Bin Clip Properties")); 1486 } 1487 // Insert listview 1488 m_itemView = new MyTreeView(this); 1489 m_layout->addWidget(m_itemView); 1490 // Info widget for failed jobs, other errors 1491 m_infoMessage = new KMessageWidget(this); 1492 m_layout->addWidget(m_infoMessage); 1493 m_infoMessage->setCloseButtonVisible(false); 1494 connect(m_infoMessage, &KMessageWidget::hideAnimationFinished, this, &Bin::slotResetInfoMessage); 1495 // m_infoMessage->setWordWrap(true); 1496 m_infoMessage->hide(); 1497 connect(this, &Bin::requesteInvalidRemoval, this, &Bin::slotQueryRemoval); 1498 connect(pCore.get(), &Core::updatePalette, this, &Bin::slotUpdatePalette); 1499 connect(m_itemModel.get(), &QAbstractItemModel::rowsInserted, this, &Bin::updateClipsCount); 1500 connect(m_itemModel.get(), &QAbstractItemModel::rowsRemoved, this, &Bin::updateClipsCount); 1501 connect(this, SIGNAL(displayBinMessage(QString, KMessageWidget::MessageType)), this, SLOT(doDisplayMessage(QString, KMessageWidget::MessageType))); 1502 wheelAccumulatedDelta = 0; 1503 } 1504 1505 Bin::~Bin() 1506 { 1507 if (m_isMainBin) { 1508 disconnect(&pCore->taskManager, &TaskManager::jobCount, m_infoLabel, &SmallJobLabel::slotSetJobCount); 1509 pCore->taskManager.slotCancelJobs(); 1510 blockSignals(true); 1511 m_proxyModel->selectionModel()->blockSignals(true); 1512 setEnabled(false); 1513 m_propertiesPanel = nullptr; 1514 abortOperations(); 1515 m_itemModel->clean(); 1516 } else { 1517 blockSignals(true); 1518 setEnabled(false); 1519 m_proxyModel->selectionModel()->blockSignals(true); 1520 } 1521 } 1522 1523 void Bin::slotUpdatePalette() 1524 { 1525 if (m_isMainBin) { 1526 // Refresh icons 1527 QIcon audioIcon = QIcon::fromTheme(QStringLiteral("audio-volume-medium")); 1528 QIcon videoIcon = QIcon::fromTheme(QStringLiteral("kdenlive-show-video")); 1529 m_audioIcon.fill(Qt::transparent); 1530 m_videoIcon.fill(Qt::transparent); 1531 QPainter p(&m_audioIcon); 1532 audioIcon.paint(&p, 0, 0, m_audioIcon.width(), m_audioIcon.height()); 1533 p.end(); 1534 QPainter p2(&m_videoIcon); 1535 videoIcon.paint(&p2, 0, 0, m_videoIcon.width(), m_videoIcon.height()); 1536 p2.end(); 1537 m_audioUsedIcon = m_audioIcon; 1538 KIconEffect::toMonochrome(m_audioUsedIcon, palette().link().color(), palette().link().color(), 1); 1539 m_videoUsedIcon = m_videoIcon; 1540 KIconEffect::toMonochrome(m_videoUsedIcon, palette().link().color(), palette().link().color(), 1); 1541 } 1542 } 1543 1544 QDockWidget *Bin::clipPropertiesDock() 1545 { 1546 return m_propertiesDock; 1547 } 1548 1549 void Bin::abortOperations() 1550 { 1551 m_infoMessage->hide(); 1552 blockSignals(true); 1553 if (m_propertiesPanel) { 1554 for (QWidget *w : m_propertiesPanel->findChildren<ClipPropertiesController *>()) { 1555 delete w; 1556 } 1557 } 1558 delete m_itemView; 1559 m_itemView = nullptr; 1560 blockSignals(false); 1561 } 1562 1563 bool Bin::eventFilter(QObject *obj, QEvent *event) 1564 { 1565 if (event->type() == QEvent::MouseButtonPress) { 1566 if (m_itemView && m_listType == BinTreeView) { 1567 // Folder state is only valid in tree view mode 1568 auto *mouseEvent = static_cast<QMouseEvent *>(event); 1569 if (mouseEvent->button() == Qt::LeftButton && mouseEvent->modifiers() & Qt::ShiftModifier) { 1570 QModelIndex idx = m_itemView->indexAt(mouseEvent->pos()); 1571 if (idx.isValid() && idx.column() == 0 && m_proxyModel) { 1572 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(idx)); 1573 if (item->itemType() == AbstractProjectItem::FolderItem) { 1574 auto *tView = static_cast<QTreeView *>(m_itemView); 1575 QRect r = tView->visualRect(idx); 1576 if (mouseEvent->pos().x() < r.x()) { 1577 if (!tView->isExpanded(idx)) { 1578 tView->expandAll(); 1579 } else { 1580 tView->collapseAll(); 1581 } 1582 return true; 1583 } 1584 } 1585 } 1586 } 1587 } 1588 } 1589 if (event->type() == QEvent::MouseButtonRelease) { 1590 if (!m_monitor->isActive()) { 1591 m_monitor->slotActivateMonitor(); 1592 } else { 1593 // Force raise 1594 m_monitor->parentWidget()->raise(); 1595 } 1596 bool success = QWidget::eventFilter(obj, event); 1597 if (m_gainedFocus) { 1598 auto *mouseEvent = static_cast<QMouseEvent *>(event); 1599 if (m_itemView) { 1600 QModelIndex idx = m_itemView->indexAt(mouseEvent->pos()); 1601 m_gainedFocus = false; 1602 if (idx.isValid() && m_proxyModel) { 1603 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(idx)); 1604 if (item->itemType() == AbstractProjectItem::ClipItem) { 1605 auto clip = std::static_pointer_cast<ProjectClip>(item); 1606 if (clip && clip->statusReady()) { 1607 editMasterEffect(item); 1608 } 1609 } else if (item->itemType() == AbstractProjectItem::SubClipItem) { 1610 auto clip = std::static_pointer_cast<ProjectSubClip>(item)->getMasterClip(); 1611 if (clip && clip->statusReady()) { 1612 editMasterEffect(item); 1613 } 1614 } 1615 } else { 1616 editMasterEffect(nullptr); 1617 } 1618 } 1619 // make sure we discard the focus indicator 1620 m_gainedFocus = false; 1621 } 1622 return success; 1623 } 1624 if (event->type() == QEvent::MouseButtonDblClick) { 1625 auto *mouseEvent = static_cast<QMouseEvent *>(event); 1626 if (m_itemView) { 1627 QModelIndex idx = m_itemView->indexAt(mouseEvent->pos()); 1628 if (!idx.isValid()) { 1629 // User double clicked on empty area 1630 slotAddClip(); 1631 } else { 1632 slotItemDoubleClicked(idx, mouseEvent->pos(), mouseEvent->modifiers()); 1633 } 1634 } else { 1635 qCDebug(KDENLIVE_LOG) << " +++++++ NO VIEW-------!!"; 1636 } 1637 return true; 1638 } 1639 if (event->type() == QEvent::Wheel) { 1640 auto *e = static_cast<QWheelEvent *>(event); 1641 if ((e != nullptr) && e->modifiers() == Qt::ControlModifier) { 1642 wheelAccumulatedDelta += e->angleDelta().y(); 1643 if (abs(wheelAccumulatedDelta) >= QWheelEvent::DefaultDeltasPerStep) { 1644 slotZoomView(wheelAccumulatedDelta > 0); 1645 } 1646 // Q_EMIT zoomView(e->delta() > 0); 1647 return true; 1648 } 1649 } 1650 return QWidget::eventFilter(obj, event); 1651 } 1652 1653 void Bin::refreshIcons() 1654 { 1655 QList<QMenu *> allMenus = this->findChildren<QMenu *>(); 1656 for (int i = 0; i < allMenus.count(); i++) { 1657 QMenu *m = allMenus.at(i); 1658 QIcon ic = m->icon(); 1659 if (ic.isNull() || ic.name().isEmpty()) { 1660 continue; 1661 } 1662 QIcon newIcon = QIcon::fromTheme(ic.name()); 1663 m->setIcon(newIcon); 1664 } 1665 QList<QToolButton *> allButtons = this->findChildren<QToolButton *>(); 1666 for (int i = 0; i < allButtons.count(); i++) { 1667 QToolButton *m = allButtons.at(i); 1668 QIcon ic = m->icon(); 1669 if (ic.isNull() || ic.name().isEmpty()) { 1670 continue; 1671 } 1672 QIcon newIcon = QIcon::fromTheme(ic.name()); 1673 m->setIcon(newIcon); 1674 } 1675 } 1676 1677 void Bin::slotSaveHeaders() 1678 { 1679 if ((m_itemView != nullptr) && m_listType == BinTreeView) { 1680 // save current treeview state (column width) 1681 auto *view = static_cast<QTreeView *>(m_itemView); 1682 m_headerInfo = view->header()->saveState(); 1683 KdenliveSettings::setTreeviewheaders(m_headerInfo.toBase64()); 1684 } 1685 } 1686 1687 void Bin::updateSortingAction(int ix) 1688 { 1689 if (ix == KdenliveSettings::binSorting()) { 1690 return; 1691 } 1692 int index = ix % 100; 1693 for (QAction *ac : m_sortGroup->actions()) { 1694 if (ac->data().toInt() == index) { 1695 ac->setChecked(true); 1696 ac->trigger(); 1697 } 1698 } 1699 if ((ix > 99) != m_sortDescend->isChecked()) { 1700 m_sortDescend->trigger(); 1701 } 1702 } 1703 1704 void Bin::slotZoomView(bool zoomIn) 1705 { 1706 wheelAccumulatedDelta = 0; 1707 if (m_itemModel->rowCount() == 0) { 1708 // Don't zoom on empty bin 1709 return; 1710 } 1711 int progress = (zoomIn) ? 1 : -1; 1712 m_slider->setValue(m_slider->value() + progress); 1713 } 1714 1715 Monitor *Bin::monitor() 1716 { 1717 return m_monitor; 1718 } 1719 1720 void Bin::slotAddClip() 1721 { 1722 // Check if we are in a folder 1723 QString parentFolder = getCurrentFolder(); 1724 ClipCreationDialog::createClipsCommand(m_doc, parentFolder, m_itemModel); 1725 pCore->window()->raiseBin(); 1726 } 1727 1728 std::shared_ptr<ProjectClip> Bin::getFirstSelectedClip() 1729 { 1730 const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); 1731 if (indexes.isEmpty()) { 1732 return std::shared_ptr<ProjectClip>(); 1733 } 1734 for (const QModelIndex &ix : indexes) { 1735 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 1736 if (item->itemType() == AbstractProjectItem::ClipItem) { 1737 auto clip = std::static_pointer_cast<ProjectClip>(item); 1738 if (clip) { 1739 return clip; 1740 } 1741 } 1742 } 1743 return nullptr; 1744 } 1745 1746 void Bin::slotDeleteClip() 1747 { 1748 const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); 1749 std::vector<std::shared_ptr<AbstractProjectItem>> items; 1750 bool included = false; 1751 bool usedFolder = false; 1752 QList<QUuid> sequences; 1753 auto checkInclusion = [](bool accum, std::shared_ptr<TreeItem> item) { 1754 return accum || std::static_pointer_cast<AbstractProjectItem>(item)->isIncludedInTimeline(); 1755 }; 1756 for (const QModelIndex &ix : indexes) { 1757 if (!ix.isValid() || ix.column() != 0) { 1758 continue; 1759 } 1760 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 1761 if (!item) { 1762 qDebug() << "Suspicious: item not found when trying to delete"; 1763 continue; 1764 } 1765 included = included || item->accumulate(false, checkInclusion); 1766 // Check if we are deleting non-empty folders: 1767 usedFolder = usedFolder || item->childCount() > 0; 1768 if (item->itemType() == AbstractProjectItem::FolderItem) { 1769 QList<std::shared_ptr<ProjectClip>> children = std::static_pointer_cast<ProjectFolder>(item)->childClips(); 1770 for (auto &c : children) { 1771 if (c->clipType() == ClipType::Timeline) { 1772 const QUuid uuid = c->getSequenceUuid(); 1773 sequences << uuid; 1774 } 1775 } 1776 1777 } else if (item->itemType() == AbstractProjectItem::ClipItem) { 1778 auto c = std::static_pointer_cast<ProjectClip>(item); 1779 if (c->clipType() == ClipType::Timeline) { 1780 const QUuid uuid = c->getSequenceUuid(); 1781 sequences << uuid; 1782 } 1783 } 1784 items.push_back(item); 1785 } 1786 if (!sequences.isEmpty()) { 1787 if (sequences.count() == m_itemModel->sequenceCount()) { 1788 KMessageBox::error(this, i18n("You cannot delete all sequences of a project")); 1789 return; 1790 } 1791 if (KMessageBox::warningContinueCancel(this, i18n("Deleting sequences cannot be undone")) != KMessageBox::Continue) { 1792 return; 1793 } 1794 for (auto seq : sequences) { 1795 pCore->projectManager()->closeTimeline(seq, true); 1796 } 1797 } 1798 if (included && (KMessageBox::warningContinueCancel(this, i18n("This will delete all selected clips from the timeline")) != KMessageBox::Continue)) { 1799 return; 1800 } 1801 if (usedFolder && (KMessageBox::warningContinueCancel(this, i18n("This will delete all folder content")) != KMessageBox::Continue)) { 1802 return; 1803 } 1804 // Clear undostack as it contains references to deleted timeline models 1805 pCore->undoStack()->clear(); 1806 Fun undo = []() { return true; }; 1807 Fun redo = []() { return true; }; 1808 // Ensure we don't delete a parent before a child 1809 // std::sort(items.begin(), items.end(), [](std::shared_ptr<AbstractProjectItem> a, std::shared_ptr<AbstractProjectItem>b) { return a->depth() > b->depth(); 1810 // }); 1811 QStringList notDeleted; 1812 for (const auto &item : items) { 1813 if (!m_itemModel->requestBinClipDeletion(item, undo, redo)) { 1814 notDeleted << item->name(); 1815 } 1816 } 1817 if (!notDeleted.isEmpty()) { 1818 KMessageBox::errorList(this, i18n("Some items could not be deleted. Maybe there are instances on locked tracks?"), notDeleted); 1819 } 1820 pCore->pushUndo(undo, redo, i18n("Delete bin Clips")); 1821 } 1822 1823 void Bin::slotReloadClip() 1824 { 1825 qDebug() << "---------\nRELOADING CLIP\n----------------"; 1826 const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); 1827 for (const QModelIndex &ix : indexes) { 1828 if (!ix.isValid() || ix.column() != 0) { 1829 continue; 1830 } 1831 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 1832 std::shared_ptr<ProjectClip> currentItem = nullptr; 1833 if (item->itemType() == AbstractProjectItem::ClipItem) { 1834 currentItem = std::static_pointer_cast<ProjectClip>(item); 1835 } else if (item->itemType() == AbstractProjectItem::SubClipItem) { 1836 currentItem = std::static_pointer_cast<ProjectSubClip>(item)->getMasterClip(); 1837 } 1838 if (currentItem) { 1839 Q_EMIT openClip(std::shared_ptr<ProjectClip>()); 1840 if (currentItem->clipStatus() == FileStatus::StatusMissing || currentItem->clipStatus() == FileStatus::StatusProxyOnly) { 1841 // Don't attempt to reload missing clip 1842 // Check if source file is available 1843 const QString sourceUrl = currentItem->url(); 1844 if (!QFile::exists(sourceUrl)) { 1845 Q_EMIT displayBinMessage(i18n("Missing source clip"), KMessageWidget::Warning); 1846 return; 1847 } 1848 } 1849 if (currentItem->clipType() == ClipType::Playlist) { 1850 // Check if a clip inside playlist is missing 1851 QString path = currentItem->url(); 1852 QFile f(path); 1853 QDomDocument doc; 1854 if (!Xml::docContentFromFile(doc, path, false)) { 1855 DocumentChecker d(QUrl::fromLocalFile(path), doc); 1856 if (!d.hasErrorInProject() && doc.documentElement().hasAttribute(QStringLiteral("modified"))) { 1857 QString backupFile = path + QStringLiteral(".backup"); 1858 KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(path), QUrl::fromLocalFile(backupFile)); 1859 if (copyjob->exec()) { 1860 if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { 1861 KMessageBox::error(this, i18n("Unable to write to file %1", path)); 1862 } else { 1863 QTextStream out(&f); 1864 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 1865 out.setCodec("UTF-8"); 1866 #endif 1867 out << doc.toString(); 1868 f.close(); 1869 KMessageBox::information( 1870 this, 1871 i18n("Your project file was modified by Kdenlive.\nTo make sure you do not lose data, a backup copy called %1 was created.", 1872 backupFile)); 1873 } 1874 } 1875 } 1876 } else { 1877 KMessageBox::error(this, i18n("Unable to parse file %1", path)); 1878 } 1879 } 1880 currentItem->reloadProducer(false, false, true); 1881 } 1882 } 1883 } 1884 1885 void Bin::replaceSingleClip(const QString clipId, const QString &newUrl) 1886 { 1887 if (newUrl.isEmpty() || !QFile::exists(newUrl)) { 1888 Q_EMIT displayBinMessage(i18n("Cannot replace clip with invalid file %1", QFileInfo(newUrl).fileName()), KMessageWidget::Information); 1889 return; 1890 } 1891 std::shared_ptr<ProjectClip> currentItem = getBinClip(clipId); 1892 if (currentItem) { 1893 QMap<QString, QString> sourceProps; 1894 QMap<QString, QString> newProps; 1895 sourceProps.insert(QStringLiteral("resource"), currentItem->url()); 1896 sourceProps.insert(QStringLiteral("kdenlive:originalurl"), currentItem->url()); 1897 sourceProps.insert(QStringLiteral("kdenlive:clipname"), currentItem->clipName()); 1898 sourceProps.insert(QStringLiteral("kdenlive:proxy"), currentItem->getProducerProperty(QStringLiteral("kdenlive:proxy"))); 1899 sourceProps.insert(QStringLiteral("_fullreload"), QStringLiteral("1")); 1900 newProps.insert(QStringLiteral("resource"), newUrl); 1901 newProps.insert(QStringLiteral("kdenlive:originalurl"), newUrl); 1902 newProps.insert(QStringLiteral("kdenlive:clipname"), QFileInfo(newUrl).fileName()); 1903 newProps.insert(QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); 1904 newProps.insert(QStringLiteral("_fullreload"), QStringLiteral("1")); 1905 // Check if replacement clip is long enough 1906 if (currentItem->hasLimitedDuration() && currentItem->isIncludedInTimeline()) { 1907 // Clip is used in timeline, make sure length is similar 1908 std::unique_ptr<Mlt::Producer> replacementProd(new Mlt::Producer(pCore->getProjectProfile(), newUrl.toUtf8().constData())); 1909 int currentDuration = int(currentItem->frameDuration()); 1910 if (replacementProd->is_valid()) { 1911 int replacementDuration = replacementProd->get_length(); 1912 if (replacementDuration < currentDuration) { 1913 if (KMessageBox::warningContinueCancel( 1914 this, i18n("You are replacing a clip with a shorter one, this might cause issues in timeline.\nReplacement is %1 frames shorter.", 1915 (currentDuration - replacementDuration))) != KMessageBox::Continue) { 1916 return; 1917 ; 1918 } 1919 } 1920 } else { 1921 KMessageBox::error(this, i18n("The selected file %1 is invalid.", newUrl)); 1922 return; 1923 } 1924 } 1925 slotEditClipCommand(currentItem->clipId(), sourceProps, newProps); 1926 } else { 1927 Q_EMIT displayBinMessage(i18n("Cannot find original clip to be replaced"), KMessageWidget::Information); 1928 } 1929 } 1930 1931 void Bin::slotReplaceClipInTimeline() 1932 { 1933 // Check if we have 2 selected clips 1934 const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); 1935 QModelIndex activeIndex = m_proxyModel->selectionModel()->currentIndex(); 1936 std::shared_ptr<AbstractProjectItem> selectedItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(activeIndex)); 1937 std::shared_ptr<AbstractProjectItem> secondaryItem = nullptr; 1938 for (const QModelIndex &ix : indexes) { 1939 if (!ix.isValid() || ix.column() != 0 || ix == activeIndex) { 1940 continue; 1941 } 1942 if (secondaryItem != nullptr) { 1943 // More than 2 clips selected, abort 1944 secondaryItem = nullptr; 1945 break; 1946 } 1947 secondaryItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 1948 } 1949 if (selectedItem == nullptr || secondaryItem == nullptr || selectedItem->itemType() != AbstractProjectItem::ClipItem || 1950 secondaryItem->itemType() != AbstractProjectItem::ClipItem) { 1951 KMessageBox::information(this, i18n("You need to select 2 clips (the replacement clip and the original clip) for timeline replacement.")); 1952 return; 1953 } 1954 // Check that we don't try to embed a sequence onto itself 1955 auto selected = std::static_pointer_cast<ProjectClip>(selectedItem); 1956 if (selected->timelineInstances().isEmpty()) { 1957 KMessageBox::information(this, i18n("The current clip <b>%1</b> is not inserted in the active timeline.", selected->clipName())); 1958 return; 1959 } 1960 auto secondary = std::static_pointer_cast<ProjectClip>(secondaryItem); 1961 if (secondary->clipType() == ClipType::Timeline && secondary->getSequenceUuid() == pCore->currentTimelineId()) { 1962 KMessageBox::information(this, i18n("You cannot insert the sequence <b>%1</b> into itself.", secondary->clipName())); 1963 return; 1964 } 1965 std::pair<bool, bool> hasAV = {selected->hasAudio(), selected->hasVideo()}; 1966 std::pair<bool, bool> secondaryHasAV = {secondary->hasAudio(), secondary->hasVideo()}; 1967 std::pair<bool, bool> replacementAV = {false, false}; 1968 if (secondaryHasAV.first && hasAV.first) { 1969 // Both clips have audio 1970 replacementAV.first = true; 1971 if (secondaryHasAV.second && hasAV.second) { 1972 // Both clips have video, ask what user wants 1973 replacementAV.second = true; 1974 } 1975 } else if (secondaryHasAV.second && hasAV.second) { 1976 replacementAV.second = true; 1977 } 1978 qDebug() << "ANALYSIS RESULT:\nMASTER: " << hasAV << "\nSECONDARY: " << secondaryHasAV << "\nRESULT: " << replacementAV; 1979 if (replacementAV.first == false && replacementAV.second == false) { 1980 KMessageBox::information(this, 1981 i18n("The selected clips %1 and %2 don't match, cannot replace audio nor video", selected->clipName(), secondary->clipName())); 1982 return; 1983 } 1984 int sourceDuration = selected->frameDuration(); 1985 int replaceDuration = secondary->frameDuration(); 1986 QDialog d(this); 1987 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 1988 auto *l = new QVBoxLayout; 1989 d.setLayout(l); 1990 if (replaceDuration != sourceDuration) { 1991 // Display respective clip duration in case of mismatch 1992 l->addWidget(new QLabel(i18n("Replacing instances of clip <b>%1</b> (%2)<br/>in active timeline\nwith clip <b>%3</b> (%4)", selected->clipName(), 1993 selected->getStringDuration(), secondary->clipName(), secondary->getStringDuration()), 1994 &d)); 1995 } else { 1996 l->addWidget(new QLabel( 1997 i18n("Replacing instances of clip <b>%1</b><br/>in active timeline\nwith clip <b>%2</b>", selected->clipName(), secondary->clipName()), &d)); 1998 } 1999 QCheckBox *cbAudio = new QCheckBox(i18n("Replace audio"), this); 2000 cbAudio->setEnabled(replacementAV.first); 2001 cbAudio->setChecked(replacementAV.first); 2002 QCheckBox *cbVideo = new QCheckBox(i18n("Replace video"), this); 2003 cbVideo->setEnabled(replacementAV.second); 2004 cbVideo->setChecked(replacementAV.second && !replacementAV.first); 2005 l->addWidget(cbAudio); 2006 l->addWidget(cbVideo); 2007 if (sourceDuration != replaceDuration) { 2008 KMessageWidget *km = new KMessageWidget(this); 2009 km->setCloseButtonVisible(false); 2010 if (sourceDuration > replaceDuration) { 2011 km->setMessageType(KMessageWidget::Warning); 2012 km->setText(i18n("Replacement clip is shorter than source clip.<br><b>Some clips in timeline might not be replaced</b>")); 2013 } else { 2014 km->setMessageType(KMessageWidget::Information); 2015 km->setText(i18n("Replacement clip duration is longer that your source clip")); 2016 } 2017 l->addWidget(km); 2018 } 2019 l->addWidget(buttonBox); 2020 d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject); 2021 d.connect(buttonBox, &QDialogButtonBox::accepted, &d, &QDialog::accept); 2022 if (d.exec() != QDialog::Accepted || (!cbVideo->isCheckable() && !cbAudio->isChecked())) { 2023 return; 2024 } 2025 pCore->projectManager()->replaceTimelineInstances(selected->clipId(), secondary->clipId(), cbAudio->isChecked(), cbVideo->isChecked()); 2026 } 2027 2028 void Bin::slotReplaceClip() 2029 { 2030 const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); 2031 QModelIndex activeSelection = m_proxyModel->selectionModel()->currentIndex(); 2032 for (const QModelIndex &ix : indexes) { 2033 if (!ix.isValid() || ix.column() != 0) { 2034 continue; 2035 } 2036 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 2037 std::shared_ptr<ProjectClip> currentItem = nullptr; 2038 if (item->itemType() == AbstractProjectItem::ClipItem) { 2039 currentItem = std::static_pointer_cast<ProjectClip>(item); 2040 if (ix == activeSelection) { 2041 qDebug() << "==== FOUND ACTIVE CLIP: " << currentItem->clipUrl(); 2042 } else { 2043 qDebug() << "==== FOUND SELECED CLIP: " << currentItem->clipUrl(); 2044 } 2045 } else if (item->itemType() == AbstractProjectItem::SubClipItem) { 2046 currentItem = std::static_pointer_cast<ProjectSubClip>(item)->getMasterClip(); 2047 } 2048 if (currentItem) { 2049 Q_EMIT openClip(std::shared_ptr<ProjectClip>()); 2050 QString fileName = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Open Replacement for %1", currentItem->clipName()), 2051 QFileInfo(currentItem->url()).absolutePath(), ClipCreationDialog::getExtensionsFilter()); 2052 if (!fileName.isEmpty()) { 2053 QMap<QString, QString> sourceProps; 2054 QMap<QString, QString> newProps; 2055 std::pair<bool, bool> hasAV = {currentItem->hasAudio(), currentItem->hasVideo()}; 2056 sourceProps.insert(QStringLiteral("resource"), currentItem->url()); 2057 sourceProps.insert(QStringLiteral("kdenlive:originalurl"), currentItem->url()); 2058 sourceProps.insert(QStringLiteral("kdenlive:clipname"), currentItem->clipName()); 2059 sourceProps.insert(QStringLiteral("kdenlive:proxy"), currentItem->getProducerProperty(QStringLiteral("kdenlive:proxy"))); 2060 sourceProps.insert(QStringLiteral("_fullreload"), QStringLiteral("1")); 2061 newProps.insert(QStringLiteral("resource"), fileName); 2062 newProps.insert(QStringLiteral("kdenlive:originalurl"), fileName); 2063 newProps.insert(QStringLiteral("kdenlive:clipname"), QFileInfo(fileName).fileName()); 2064 newProps.insert(QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); 2065 newProps.insert(QStringLiteral("_fullreload"), QStringLiteral("1")); 2066 // Check if replacement clip is long enough 2067 if (currentItem->hasLimitedDuration() && currentItem->isIncludedInTimeline()) { 2068 // Clip is used in timeline, make sure length is similar 2069 std::unique_ptr<Mlt::Producer> replacementProd(new Mlt::Producer(pCore->getProjectProfile(), fileName.toUtf8().constData())); 2070 int currentDuration = int(currentItem->frameDuration()); 2071 if (replacementProd->is_valid()) { 2072 replacementProd->probe(); 2073 std::pair<bool, bool> replacementHasAV = { 2074 (replacementProd->property_exists("audio_index") && replacementProd->get_int("audio_index") > -1), 2075 (replacementProd->property_exists("video_index") && replacementProd->get_int("video_index") > -1)}; 2076 if (hasAV != replacementHasAV) { 2077 replacementProd.reset(); 2078 QString message; 2079 std::pair<bool, bool> replaceAV = {false, false}; 2080 if (hasAV.first && hasAV.second) { 2081 if (!replacementHasAV.second) { 2082 // Original clip has audio and video, replacement has only audio 2083 replaceAV.first = true; 2084 message = i18n("Replacement clip does not contain a video stream. Do you want to replace only the audio component of this " 2085 "clip in the active timeline ?"); 2086 } else if (!replacementHasAV.first) { 2087 // Original clip has audio and video, replacement has only video 2088 replaceAV.second = true; 2089 message = i18n("Replacement clip does not contain an audio stream. Do you want to replace only the video component of this " 2090 "clip in the active timeline ?"); 2091 } 2092 if (replaceAV.first == false && replaceAV.second == false) { 2093 KMessageBox::information(this, i18n("You cannot replace a clip with a different type of clip (audio/video not matching).")); 2094 return; 2095 } 2096 if (KMessageBox::questionTwoActions(QApplication::activeWindow(), message, {}, KGuiItem(i18n("Replace in timeline")), 2097 KStandardGuiItem::cancel()) == KMessageBox::SecondaryAction) { 2098 return; 2099 } 2100 // Replace only one component. 2101 QString folder = QStringLiteral("-1"); 2102 auto containingFolder = std::static_pointer_cast<ProjectFolder>(currentItem->parent()); 2103 if (containingFolder) { 2104 folder = containingFolder->clipId(); 2105 } 2106 Fun undo = []() { return true; }; 2107 Fun redo = []() { return true; }; 2108 std::function<void(const QString &)> callBack = [replaceAudio = replaceAV.first, 2109 sourceId = currentItem->clipId()](const QString &binId) { 2110 qDebug() << "::: READY TO REPLACE: " << sourceId << ", WITH: " << binId; 2111 if (!binId.isEmpty()) { 2112 pCore->projectManager()->replaceTimelineInstances(sourceId, binId, replaceAudio, !replaceAudio); 2113 } 2114 return true; 2115 }; 2116 const QString clipId = ClipCreator::createClipFromFile(fileName, folder, m_itemModel, undo, redo, callBack); 2117 pCore->pushUndo(undo, redo, i18nc("@action", "Add clip")); 2118 } else if ((hasAV.first && !replacementHasAV.first) || (hasAV.second && !replacementHasAV.second)) { 2119 KMessageBox::information(this, i18n("You cannot replace a clip with a different type of clip (audio/video not matching).")); 2120 } 2121 return; 2122 } 2123 int replacementDuration = replacementProd->get_length(); 2124 if (replacementDuration < currentDuration) { 2125 if (KMessageBox::warningContinueCancel( 2126 this, 2127 i18n("You are replacing a clip with a shorter one, this might cause issues in timeline.\nReplacement is %1 frames shorter.", 2128 (currentDuration - replacementDuration))) != KMessageBox::Continue) { 2129 continue; 2130 } 2131 } 2132 } else { 2133 KMessageBox::error(this, i18n("The selected file %1 is invalid.", fileName)); 2134 continue; 2135 } 2136 } 2137 slotEditClipCommand(currentItem->clipId(), sourceProps, newProps); 2138 } 2139 } 2140 } 2141 } 2142 2143 void Bin::slotLocateClip() 2144 { 2145 const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); 2146 for (const QModelIndex &ix : indexes) { 2147 if (!ix.isValid() || ix.column() != 0) { 2148 continue; 2149 } 2150 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 2151 std::shared_ptr<ProjectClip> currentItem = nullptr; 2152 if (item->itemType() == AbstractProjectItem::ClipItem) { 2153 currentItem = std::static_pointer_cast<ProjectClip>(item); 2154 } else if (item->itemType() == AbstractProjectItem::SubClipItem) { 2155 currentItem = std::static_pointer_cast<ProjectSubClip>(item)->getMasterClip(); 2156 } 2157 if (currentItem) { 2158 QUrl url = QUrl::fromLocalFile(currentItem->url()); //.adjusted(QUrl::RemoveFilename); 2159 bool exists = QFile(url.toLocalFile()).exists(); 2160 if (currentItem->hasUrl() && exists) { 2161 KIO::highlightInFileManager({url}); 2162 qCDebug(KDENLIVE_LOG) << " / / " + url.toString(); 2163 } else { 2164 if (!exists) { 2165 pCore->displayMessage(i18n("Could not locate %1", url.toString()), MessageType::ErrorMessage, 300); 2166 } 2167 return; 2168 } 2169 } 2170 } 2171 } 2172 2173 void Bin::slotDuplicateClip() 2174 { 2175 const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); 2176 QList<std::shared_ptr<AbstractProjectItem>> items; 2177 for (const QModelIndex &ix : indexes) { 2178 if (!ix.isValid() || ix.column() != 0) { 2179 continue; 2180 } 2181 items << m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 2182 } 2183 std::function<void(const QString &)> callBack = [this](const QString &binId) { 2184 if (!binId.isEmpty()) { 2185 selectClipById(binId); 2186 std::shared_ptr<ProjectClip> clip = getBinClip(binId); 2187 if (clip && clip->clipType() == ClipType::Timeline) { 2188 // For duplicated timeline clips, we need to build the timelinemodel otherwise the producer is not correctly saved 2189 const QUuid uuid = clip->getSequenceUuid(); 2190 return pCore->projectManager()->openTimeline(binId, uuid, -1, true); 2191 } 2192 } 2193 return true; 2194 }; 2195 int ix = 0; 2196 QString lastId; 2197 for (const auto &item : qAsConst(items)) { 2198 ix++; 2199 if (item->itemType() == AbstractProjectItem::ClipItem) { 2200 auto currentItem = std::static_pointer_cast<ProjectClip>(item); 2201 if (currentItem) { 2202 if (currentItem->clipType() == ClipType::Timeline) { 2203 const QUuid uuid = currentItem->getSequenceUuid(); 2204 if (m_doc->getTimelinesUuids().contains(uuid)) { 2205 // Sync last changes for this timeline if it is opened 2206 m_doc->storeGroups(uuid); 2207 pCore->projectManager()->syncTimeline(uuid, true); 2208 } 2209 QTemporaryFile src(QDir::temp().absoluteFilePath(QString("XXXXXX.mlt"))); 2210 src.setAutoRemove(false); 2211 if (!src.open()) { 2212 pCore->displayMessage(i18n("Could not create temporary file in %1", QDir::temp().absolutePath()), MessageType::ErrorMessage, 500); 2213 return; 2214 } 2215 // Save playlist to disk 2216 currentItem->cloneProducerToFile(src.fileName()); 2217 // extract xml 2218 QDomDocument xml = ClipCreator::getXmlFromUrl(src.fileName()); 2219 if (xml.isNull()) { 2220 pCore->displayMessage(i18n("Duplicating sequence failed"), MessageType::ErrorMessage, 500); 2221 return; 2222 } 2223 QDomDocument doc; 2224 if (!Xml::docContentFromFile(doc, src.fileName(), false)) { 2225 return; 2226 } 2227 const QByteArray result = doc.toString().toUtf8(); 2228 std::shared_ptr<Mlt::Producer> xmlProd(new Mlt::Producer(pCore->getProjectProfile(), "xml-string", result.constData())); 2229 QString id; 2230 Fun undo = []() { return true; }; 2231 Fun redo = []() { return true; }; 2232 xmlProd->set("kdenlive:clipname", i18n("%1 (copy)", currentItem->clipName()).toUtf8().constData()); 2233 xmlProd->set("kdenlive:sequenceproperties.documentuuid", m_doc->uuid().toString().toUtf8().constData()); 2234 m_itemModel->requestAddBinClip(id, xmlProd, item->parent()->clipId(), undo, redo, callBack); 2235 pCore->pushUndo(undo, redo, i18n("Duplicate clip")); 2236 } else { 2237 QDomDocument doc; 2238 QDomElement xml = currentItem->toXml(doc); 2239 if (!xml.isNull()) { 2240 QString currentName = Xml::getXmlProperty(xml, QStringLiteral("kdenlive:clipname")); 2241 if (currentName.isEmpty()) { 2242 QUrl url = QUrl::fromLocalFile(Xml::getXmlProperty(xml, QStringLiteral("resource"))); 2243 if (url.isValid()) { 2244 currentName = url.fileName(); 2245 } 2246 } 2247 if (!currentName.isEmpty()) { 2248 currentName.append(i18nc("append to clip name to indicate a copied idem", " (copy)")); 2249 Xml::setXmlProperty(xml, QStringLiteral("kdenlive:clipname"), currentName); 2250 } 2251 QString id; 2252 if (ix == items.count()) { 2253 m_itemModel->requestAddBinClip(id, xml, item->parent()->clipId(), i18n("Duplicate clip"), callBack); 2254 } else { 2255 m_itemModel->requestAddBinClip(id, xml, item->parent()->clipId(), i18n("Duplicate clip")); 2256 } 2257 } 2258 } 2259 } 2260 } else if (item->itemType() == AbstractProjectItem::SubClipItem) { 2261 auto currentItem = std::static_pointer_cast<ProjectSubClip>(item); 2262 QString id; 2263 QPoint clipZone = currentItem->zone(); 2264 m_itemModel->requestAddBinSubClip(id, clipZone.x(), clipZone.y(), {}, currentItem->getMasterClip()->clipId()); 2265 lastId = id; 2266 } 2267 } 2268 if (!lastId.isEmpty()) { 2269 selectClipById(lastId); 2270 } 2271 } 2272 2273 void Bin::setMonitor(Monitor *monitor) 2274 { 2275 m_monitor = monitor; 2276 connect(m_monitor, &Monitor::addClipToProject, this, &Bin::slotAddClipToProject); 2277 connect(m_monitor, &Monitor::refreshCurrentClip, this, &Bin::slotOpenCurrent); 2278 connect(m_itemModel.get(), &ProjectItemModel::resetPlayOrLoopZone, m_monitor, &Monitor::resetPlayOrLoopZone, Qt::DirectConnection); 2279 connect(this, &Bin::openClip, [&](std::shared_ptr<ProjectClip> clip, int in, int out) { 2280 m_monitor->slotOpenClip(clip, in, out); 2281 if (clip && clip->hasLimitedDuration()) { 2282 clip->refreshBounds(); 2283 } 2284 pCore->textEditWidget()->openClip(clip); 2285 }); 2286 } 2287 2288 void Bin::cleanDocument() 2289 { 2290 blockSignals(true); 2291 if (m_proxyModel) { 2292 m_proxyModel->selectionModel()->blockSignals(true); 2293 } 2294 setEnabled(false); 2295 // Cleanup references in the clip properties dialog 2296 Q_EMIT requestShowClipProperties(nullptr); 2297 2298 // Cleanup previous project 2299 m_itemModel->clean(); 2300 if (m_propertiesPanel) { 2301 m_propertiesPanel->setProperty("clipId", QString()); 2302 for (QWidget *w : m_propertiesPanel->findChildren<ClipPropertiesController *>()) { 2303 delete w; 2304 } 2305 } 2306 delete m_itemView; 2307 m_itemView = nullptr; 2308 isLoading = false; 2309 shouldCheckProfile = false; 2310 pCore->textEditWidget()->openClip(nullptr); 2311 } 2312 2313 const QString Bin::setDocument(KdenliveDoc *project, const QString &id) 2314 { 2315 m_doc = project; 2316 QString folderName; 2317 if (m_isMainBin) { 2318 m_infoLabel->slotSetJobCount(0); 2319 } 2320 int iconHeight = int(QFontInfo(font()).pixelSize() * 3.5); 2321 m_baseIconSize = QSize(int(iconHeight * pCore->getCurrentDar()), iconHeight); 2322 setEnabled(true); 2323 blockSignals(false); 2324 if (m_proxyModel) { 2325 m_proxyModel->selectionModel()->blockSignals(false); 2326 } 2327 // reset filtering 2328 QSignalBlocker bk(m_filterButton); 2329 m_filterButton->setChecked(false); 2330 m_filterButton->setToolTip(i18n("Filter")); 2331 connect(m_proxyAction, &QAction::toggled, m_doc, [&](bool doProxy) { m_doc->slotProxyCurrentItem(doProxy); }); 2332 2333 // connect(m_itemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), m_itemView 2334 // connect(m_itemModel, SIGNAL(updateCurrentItem()), this, SLOT(autoSelect())); 2335 slotInitView(nullptr); 2336 bool binEffectsDisabled = getDocumentProperty(QStringLiteral("disablebineffects")).toInt() == 1; 2337 // Set media browser url 2338 QString url = getDocumentProperty(QStringLiteral("browserurl")); 2339 const QString sorting = getDocumentProperty(QStringLiteral("binsort")); 2340 if (!sorting.isEmpty()) { 2341 int binSorting = sorting.toInt(); 2342 updateSortingAction(binSorting); 2343 } 2344 if (!url.isEmpty()) { 2345 if (QFileInfo(url).isRelative()) { 2346 url.prepend(m_doc->documentRoot()); 2347 } 2348 pCore->mediaBrowser()->setUrl(QUrl::fromLocalFile(url)); 2349 } 2350 QAction *disableEffects = pCore->window()->actionCollection()->action(QStringLiteral("disable_bin_effects")); 2351 if (disableEffects) { 2352 if (binEffectsDisabled != disableEffects->isChecked()) { 2353 QSignalBlocker effectBk(disableEffects); 2354 disableEffects->setChecked(binEffectsDisabled); 2355 } 2356 } 2357 m_itemModel->setBinEffectsEnabled(!binEffectsDisabled); 2358 if (!id.isEmpty()) { 2359 // Open view in a specific folder 2360 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getItemByBinId(id); 2361 auto parentIx = m_itemModel->getIndexFromItem(item); 2362 m_itemView->setRootIndex(m_proxyModel->mapFromSource(parentIx)); 2363 folderName = item->name(); 2364 m_upAction->setEnabled(true); 2365 m_upAction->setVisible(true); 2366 } 2367 2368 // setBinEffectsEnabled(!binEffectsDisabled, false); 2369 QMap<int, QStringList> projectTags = m_doc->getProjectTags(); 2370 m_tagsWidget->rebuildTags(projectTags); 2371 rebuildFilters(projectTags.size()); 2372 return folderName; 2373 } 2374 2375 void Bin::rebuildFilters(int tagsCount) 2376 { 2377 m_filterMenu->clear(); 2378 2379 // Add clear entry 2380 QAction *clearFilter = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Filters"), this); 2381 m_filterMenu->addAction(clearFilter); 2382 2383 // Add tag filters 2384 m_filterMenu->addSeparator(); 2385 2386 QAction *tagFilter = new QAction(i18n("No Tags"), &m_filterTagGroup); 2387 tagFilter->setData(QStringLiteral("#")); 2388 tagFilter->setCheckable(true); 2389 m_filterMenu->addAction(tagFilter); 2390 2391 for (int i = 1; i <= tagsCount; i++) { 2392 QAction *tag = pCore->window()->actionCollection()->action(QString("tag_%1").arg(i)); 2393 if (tag) { 2394 QAction *tagFilter = new QAction(tag->icon(), tag->text(), &m_filterTagGroup); 2395 tagFilter->setData(tag->data()); 2396 tagFilter->setCheckable(true); 2397 m_filterMenu->addAction(tagFilter); 2398 } 2399 } 2400 2401 // Add rating filters 2402 m_filterMenu->addSeparator(); 2403 2404 for (int i = 0; i < 6; ++i) { 2405 auto *rateFilter = new QAction(QIcon::fromTheme(QStringLiteral("favorite")), i18np("%1 Star", "%1 Stars", i), &m_filterRateGroup); 2406 rateFilter->setData(QString(".%1").arg(2 * i)); 2407 rateFilter->setCheckable(true); 2408 m_filterMenu->addAction(rateFilter); 2409 } 2410 // Add usage filter 2411 m_filterMenu->addSeparator(); 2412 auto *usageMenu = new QMenu(i18n("Filter by Usage"), m_filterMenu); 2413 m_filterMenu->addMenu(usageMenu); 2414 2415 auto *usageFilter = new QAction(i18n("All Clips"), &m_filterUsageGroup); 2416 usageFilter->setData(ProjectSortProxyModel::All); 2417 usageFilter->setCheckable(true); 2418 usageFilter->setChecked(true); 2419 usageMenu->addAction(usageFilter); 2420 usageFilter = new QAction(i18n("Unused Clips"), &m_filterUsageGroup); 2421 usageFilter->setData(ProjectSortProxyModel::Unused); 2422 usageFilter->setCheckable(true); 2423 usageMenu->addAction(usageFilter); 2424 usageFilter = new QAction(i18n("Used Clips"), &m_filterUsageGroup); 2425 usageFilter->setData(ProjectSortProxyModel::Used); 2426 usageFilter->setCheckable(true); 2427 usageMenu->addAction(usageFilter); 2428 2429 // Add type filters 2430 m_filterMenu->addSeparator(); 2431 auto *typeMenu = new QMenu(i18n("Filter by Type"), m_filterMenu); 2432 m_filterMenu->addMenu(typeMenu); 2433 m_filterMenu->addSeparator(); 2434 auto *typeFilter = new QAction(QIcon::fromTheme(QStringLiteral("video-x-generic")), i18n("AV Clip"), &m_filterTypeGroup); 2435 typeFilter->setData(ClipType::AV); 2436 typeFilter->setCheckable(true); 2437 typeMenu->addAction(typeFilter); 2438 typeFilter = new QAction(QIcon::fromTheme(QStringLiteral("video-x-matroska")), i18n("Mute Video"), &m_filterTypeGroup); 2439 typeFilter->setData(ClipType::Video); 2440 typeFilter->setCheckable(true); 2441 typeMenu->addAction(typeFilter); 2442 typeFilter = new QAction(QIcon::fromTheme(QStringLiteral("audio-x-generic")), i18n("Audio"), &m_filterTypeGroup); 2443 typeFilter->setData(ClipType::Audio); 2444 typeFilter->setCheckable(true); 2445 typeMenu->addAction(typeFilter); 2446 typeFilter = new QAction(QIcon::fromTheme(QStringLiteral("image-jpeg")), i18n("Image"), &m_filterTypeGroup); 2447 typeFilter->setData(ClipType::Image); 2448 typeFilter->setCheckable(true); 2449 typeMenu->addAction(typeFilter); 2450 typeFilter = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-add-slide-clip")), i18n("Slideshow"), &m_filterTypeGroup); 2451 typeFilter->setData(ClipType::SlideShow); 2452 typeFilter->setCheckable(true); 2453 typeMenu->addAction(typeFilter); 2454 typeFilter = new QAction(QIcon::fromTheme(QStringLiteral("video-mlt-playlist")), i18n("Playlist"), &m_filterTypeGroup); 2455 typeFilter->setData(ClipType::Playlist); 2456 typeFilter->setCheckable(true); 2457 typeMenu->addAction(typeFilter); 2458 typeFilter = new QAction(QIcon::fromTheme(QStringLiteral("video-mlt-playlist")), i18n("Sequences"), &m_filterTypeGroup); 2459 typeFilter->setData(ClipType::Timeline); 2460 typeFilter->setCheckable(true); 2461 typeMenu->addAction(typeFilter); 2462 typeFilter = new QAction(QIcon::fromTheme(QStringLiteral("draw-text")), i18n("Title"), &m_filterTypeGroup); 2463 typeFilter->setData(ClipType::Text); 2464 typeFilter->setCheckable(true); 2465 typeMenu->addAction(typeFilter); 2466 typeFilter = new QAction(QIcon::fromTheme(QStringLiteral("draw-text")), i18n("Title Template"), &m_filterTypeGroup); 2467 typeFilter->setData(ClipType::TextTemplate); 2468 typeFilter->setCheckable(true); 2469 typeMenu->addAction(typeFilter); 2470 typeFilter = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-add-color-clip")), i18n("Color"), &m_filterTypeGroup); 2471 typeFilter->setData(ClipType::Color); 2472 typeFilter->setCheckable(true); 2473 typeMenu->addAction(typeFilter); 2474 } 2475 2476 void Bin::slotApplyFilters() 2477 { 2478 QList<QAction *> list = m_filterMenu->actions(); 2479 QList<int> rateFilters; 2480 QList<int> typeFilters; 2481 ProjectSortProxyModel::UsageFilter usageFilter = ProjectSortProxyModel::UsageFilter(m_filterUsageGroup.checkedAction()->data().toInt()); 2482 QStringList tagFilters; 2483 for (QAction *ac : qAsConst(list)) { 2484 if (ac->isChecked()) { 2485 QString actionData = ac->data().toString(); 2486 if (actionData.startsWith(QLatin1Char('#'))) { 2487 // Filter by tag 2488 tagFilters << actionData; 2489 } else if (actionData.startsWith(QLatin1Char('.'))) { 2490 // Filter by rating 2491 rateFilters << actionData.remove(0, 1).toInt(); 2492 } 2493 } 2494 } 2495 // Type actions 2496 list = m_filterTypeGroup.actions(); 2497 for (QAction *ac : qAsConst(list)) { 2498 if (ac->isChecked()) { 2499 typeFilters << ac->data().toInt(); 2500 } 2501 } 2502 QSignalBlocker bkt(m_filterButton); 2503 if (!rateFilters.isEmpty() || !tagFilters.isEmpty() || !typeFilters.isEmpty() || usageFilter != ProjectSortProxyModel::All) { 2504 m_filterButton->setChecked(true); 2505 } else { 2506 m_filterButton->setChecked(false); 2507 } 2508 m_proxyModel->slotSetFilters(tagFilters, rateFilters, typeFilters, usageFilter); 2509 } 2510 2511 void Bin::createClip(const QDomElement &xml) 2512 { 2513 // Check if clip should be in a folder 2514 QString groupId = ProjectClip::getXmlProperty(xml, QStringLiteral("kdenlive:folderid")); 2515 std::shared_ptr<ProjectFolder> parentFolder = m_itemModel->getFolderByBinId(groupId); 2516 if (!parentFolder) { 2517 parentFolder = m_itemModel->getRootFolder(); 2518 } 2519 QString path = Xml::getXmlProperty(xml, QStringLiteral("resource")); 2520 if (path.endsWith(QStringLiteral(".mlt")) || path.endsWith(QStringLiteral(".kdenlive"))) { 2521 QDomDocument doc; 2522 if (!Xml::docContentFromFile(doc, path, false)) { 2523 return; 2524 } 2525 DocumentChecker d(QUrl::fromLocalFile(path), doc); 2526 if (!d.hasErrorInProject() && doc.documentElement().hasAttribute(QStringLiteral("modified"))) { 2527 QString backupFile = path + QStringLiteral(".backup"); 2528 KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(path), QUrl::fromLocalFile(backupFile)); 2529 if (copyjob->exec()) { 2530 QFile f(path); 2531 if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { 2532 KMessageBox::error(this, i18n("Unable to write to file %1", path)); 2533 } else { 2534 QTextStream out(&f); 2535 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 2536 out.setCodec("UTF-8"); 2537 #endif 2538 out << doc.toString(); 2539 f.close(); 2540 KMessageBox::information( 2541 this, i18n("Your project file was modified by Kdenlive.\nTo make sure you do not lose data, a backup copy called %1 was created.", 2542 backupFile)); 2543 } 2544 } 2545 } 2546 } 2547 QString id = Xml::getTagContentByAttribute(xml, QStringLiteral("property"), QStringLiteral("name"), QStringLiteral("kdenlive:id")); 2548 if (id.isEmpty()) { 2549 id = QString::number(m_itemModel->getFreeClipId()); 2550 } 2551 auto newClip = ProjectClip::construct(id, xml, m_blankThumb, m_itemModel); 2552 parentFolder->appendChild(newClip); 2553 } 2554 2555 void Bin::slotAddFolder() 2556 { 2557 auto parentFolder = m_itemModel->getFolderByBinId(getCurrentFolder()); 2558 qDebug() << "parent folder id" << parentFolder->clipId(); 2559 QString newId; 2560 Fun undo = []() { return true; }; 2561 Fun redo = []() { return true; }; 2562 m_itemModel->requestAddFolder(newId, i18n("Folder"), parentFolder->clipId(), undo, redo); 2563 pCore->pushUndo(undo, redo, i18n("Create bin folder")); 2564 if (m_listType == BinTreeView) { 2565 // Make sure parent folder is expanded 2566 if (parentFolder->clipId().toInt() > -1) { 2567 auto parentIx = m_itemModel->getIndexFromItem(parentFolder); 2568 auto *view = static_cast<QTreeView *>(m_itemView); 2569 view->expand(m_proxyModel->mapFromSource(parentIx)); 2570 } 2571 } 2572 2573 // Edit folder name 2574 auto folder = m_itemModel->getFolderByBinId(newId); 2575 auto ix = m_itemModel->getIndexFromItem(folder); 2576 2577 // Scroll to ensure folder is visible 2578 m_itemView->scrollTo(m_proxyModel->mapFromSource(ix), QAbstractItemView::EnsureVisible); 2579 qDebug() << "selecting" << ix; 2580 if (ix.isValid()) { 2581 qDebug() << "ix valid"; 2582 m_proxyModel->selectionModel()->clearSelection(); 2583 int row = ix.row(); 2584 const QModelIndex id = m_itemModel->index(row, 0, ix.parent()); 2585 const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, ix.parent()); 2586 if (id.isValid() && id2.isValid()) { 2587 m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), 2588 QItemSelectionModel::Select); 2589 } 2590 m_itemView->edit(m_proxyModel->mapFromSource(ix)); 2591 } 2592 } 2593 2594 void Bin::ensureCurrent() 2595 { 2596 const QModelIndexList indexes = m_proxyModel->selectionModel()->selection().indexes(); 2597 for (const QModelIndex &ix : indexes) { 2598 if (!ix.isValid() || ix.column() != 0) { 2599 continue; 2600 } 2601 m_itemView->setCurrentIndex(ix); 2602 } 2603 } 2604 2605 QModelIndex Bin::getIndexForId(const QString &id, bool folderWanted) const 2606 { 2607 QModelIndexList items = m_itemModel->match(m_itemModel->index(0, 0), AbstractProjectItem::DataId, QVariant::fromValue(id), 1, Qt::MatchRecursive); 2608 for (int i = 0; i < items.count(); i++) { 2609 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(items.at(i)); 2610 AbstractProjectItem::PROJECTITEMTYPE type = currentItem->itemType(); 2611 if (folderWanted && type == AbstractProjectItem::FolderItem) { 2612 // We found our folder 2613 return items.at(i); 2614 } 2615 if (!folderWanted && type == AbstractProjectItem::ClipItem) { 2616 // We found our clip 2617 return items.at(i); 2618 } 2619 } 2620 return {}; 2621 } 2622 2623 void Bin::selectAll() 2624 { 2625 QModelIndex rootIndex = m_itemView->rootIndex(); 2626 QModelIndex currentSelection = m_proxyModel->selectionModel()->currentIndex(); 2627 if (currentSelection.isValid()) { 2628 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(currentSelection)); 2629 if (currentItem) { 2630 std::shared_ptr<ProjectFolder> parentFolder = std::static_pointer_cast<ProjectFolder>(currentItem->getEnclosingFolder()); 2631 if (parentFolder) { 2632 rootIndex = m_proxyModel->mapFromSource(getIndexForId(parentFolder->clipId(), true)); 2633 } 2634 } 2635 } 2636 m_proxyModel->selectAll(rootIndex); 2637 } 2638 2639 void Bin::selectClipById(const QString &clipId, int frame, const QPoint &zone, bool activateMonitor) 2640 { 2641 if (m_monitor->activeClipId() == clipId) { 2642 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(clipId); 2643 if (clip) { 2644 QModelIndex ix = m_itemModel->getIndexFromItem(clip); 2645 int row = ix.row(); 2646 const QModelIndex id = m_itemModel->index(row, 0, ix.parent()); 2647 const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, ix.parent()); 2648 if (id.isValid() && id2.isValid()) { 2649 m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), 2650 QItemSelectionModel::SelectCurrent); 2651 } 2652 m_itemView->scrollTo(m_proxyModel->mapFromSource(ix), QAbstractItemView::EnsureVisible); 2653 } 2654 } else { 2655 std::shared_ptr<ProjectClip> clip = getBinClip(clipId); 2656 if (clip == nullptr) { 2657 return; 2658 } 2659 selectClip(clip); 2660 } 2661 if (!zone.isNull()) { 2662 m_monitor->slotLoadClipZone(zone); 2663 } 2664 if (activateMonitor) { 2665 if (frame > -1) { 2666 m_monitor->slotSeek(frame); 2667 m_monitor->refreshMonitor(); 2668 } else { 2669 m_monitor->slotActivateMonitor(); 2670 } 2671 } 2672 } 2673 2674 void Bin::selectProxyModel(const QModelIndex &id) 2675 { 2676 if (isLoading) { 2677 // return; 2678 } 2679 if (id.isValid()) { 2680 if (id.column() != 0 && m_monitor->activeClipId() == QString::number(int(id.internalId()))) { 2681 return; 2682 } 2683 QString clipService; 2684 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(id)); 2685 if (currentItem) { 2686 // Set item as current so that it displays its content in clip monitor 2687 setCurrent(currentItem); 2688 AbstractProjectItem::PROJECTITEMTYPE itemType = currentItem->itemType(); 2689 bool isClip = itemType == AbstractProjectItem::ClipItem; 2690 bool isFolder = itemType == AbstractProjectItem::FolderItem; 2691 std::shared_ptr<ProjectClip> clip = nullptr; 2692 if (isClip) { 2693 clip = std::static_pointer_cast<ProjectClip>(currentItem); 2694 m_tagsWidget->setTagData(clip->tags()); 2695 m_deleteAction->setText(i18n("Delete Clip")); 2696 m_deleteAction->setWhatsThis( 2697 xi18nc("@info:whatsthis", "Deletes the currently selected clips from the project bin and also from the timeline.")); 2698 m_proxyAction->setText(i18n("Proxy Clip")); 2699 } else if (itemType == AbstractProjectItem::FolderItem) { 2700 // A folder was selected, disable editing clip 2701 m_tagsWidget->setTagData(); 2702 m_deleteAction->setText(i18n("Delete Folder")); 2703 m_proxyAction->setText(i18n("Proxy Folder")); 2704 } else if (itemType == AbstractProjectItem::SubClipItem) { 2705 m_tagsWidget->setTagData(currentItem->tags()); 2706 auto subClip = std::static_pointer_cast<ProjectSubClip>(currentItem); 2707 clip = subClip->getMasterClip(); 2708 m_deleteAction->setText(i18n("Delete Clip")); 2709 m_deleteAction->setWhatsThis( 2710 xi18nc("@info:whatsthis", "Deletes the currently selected clips from the project bin and also from the timeline.")); 2711 m_proxyAction->setText(i18n("Proxy Clip")); 2712 } 2713 bool isImported = false; 2714 bool hasAudio = false; 2715 ClipType::ProducerType type = ClipType::Unknown; 2716 // don't need to wait for the clip to be ready to get its type 2717 if (clip) { 2718 type = clip->clipType(); 2719 } 2720 if (clip && clip->statusReady()) { 2721 Q_EMIT requestShowClipProperties(clip, false); 2722 m_proxyAction->blockSignals(true); 2723 clipService = clip->getProducerProperty(QStringLiteral("mlt_service")); 2724 hasAudio = clip->hasAudio(); 2725 m_proxyAction->setChecked(clip->hasProxy()); 2726 m_proxyAction->blockSignals(false); 2727 if (clip->hasUrl()) { 2728 isImported = true; 2729 } 2730 } else if (clip && clip->clipStatus() == FileStatus::StatusMissing) { 2731 m_openAction->setEnabled(false); 2732 } else { 2733 // Disable find in timeline option 2734 m_openAction->setEnabled(false); 2735 } 2736 if (m_clipsActionsMenu) { 2737 m_clipsActionsMenu->setEnabled(!isFolder); 2738 } 2739 m_editAction->setVisible(!isFolder); 2740 m_editAction->setEnabled(true); 2741 m_extractAudioAction->menuAction()->setVisible(hasAudio); 2742 m_extractAudioAction->setEnabled(hasAudio); 2743 m_openAction->setEnabled(type == ClipType::Image || type == ClipType::Audio || type == ClipType::TextTemplate || type == ClipType::Text || 2744 type == ClipType::Animation); 2745 m_openAction->setVisible(!isFolder); 2746 m_duplicateAction->setEnabled(isClip); 2747 m_duplicateAction->setVisible(!isFolder); 2748 if (m_inTimelineAction) { 2749 m_inTimelineAction->setEnabled(isClip); 2750 m_inTimelineAction->setVisible(isClip); 2751 } 2752 m_locateAction->setEnabled(!isFolder && isImported); 2753 m_locateAction->setVisible(!isFolder && isImported); 2754 m_proxyAction->setEnabled(m_doc->useProxy() && !isFolder); 2755 m_reloadAction->setEnabled(isClip && type != ClipType::Timeline); 2756 m_reloadAction->setVisible(!isFolder); 2757 m_replaceAction->setEnabled(isClip); 2758 m_replaceAction->setVisible(!isFolder); 2759 m_replaceInTimelineAction->setEnabled(isClip); 2760 m_replaceInTimelineAction->setVisible(!isFolder); 2761 // Enable actions depending on clip type 2762 for (auto &a : m_clipsActionsMenu->actions()) { 2763 qDebug() << "ACTION: " << a->text() << " = " << a->data().toString(); 2764 QString actionType = a->data().toString().section(QLatin1Char(';'), 1); 2765 qDebug() << ":::: COMPARING ACTIONTYPE: " << actionType << " = " << type; 2766 if (actionType.isEmpty()) { 2767 a->setEnabled(true); 2768 } else if (actionType.contains(QLatin1Char('v')) && (type == ClipType::AV || type == ClipType::Video)) { 2769 a->setEnabled(true); 2770 } else if (actionType.contains(QLatin1Char('a')) && (type == ClipType::AV || type == ClipType::Audio)) { 2771 a->setEnabled(true); 2772 } else if (actionType.contains(QLatin1Char('i')) && type == ClipType::Image) { 2773 a->setEnabled(true); 2774 } else { 2775 a->setEnabled(false); 2776 } 2777 } 2778 m_clipsActionsMenu->menuAction()->setVisible(!isFolder && (type == ClipType::AV || type == ClipType::Timeline || type == ClipType::Playlist || 2779 type == ClipType::Image || type == ClipType::Video || type == ClipType::Audio)); 2780 2781 m_transcodeAction->setEnabled(!isFolder); 2782 m_transcodeAction->setVisible(!isFolder && (type == ClipType::Playlist || type == ClipType::Timeline || type == ClipType::Text || 2783 clipService.contains(QStringLiteral("avformat")))); 2784 2785 m_deleteAction->setEnabled(true); 2786 m_renameAction->setEnabled(true); 2787 updateClipsCount(); 2788 return; 2789 } 2790 } else { 2791 // No item selected in bin 2792 Q_EMIT requestShowClipProperties(nullptr); 2793 Q_EMIT requestClipShow(nullptr); 2794 // clear effect stack 2795 Q_EMIT requestShowEffectStack(QString(), nullptr, QSize(), false); 2796 // Display black bg in clip monitor 2797 Q_EMIT openClip(std::shared_ptr<ProjectClip>()); 2798 } 2799 m_editAction->setEnabled(false); 2800 m_clipsActionsMenu->setEnabled(false); 2801 m_extractAudioAction->setEnabled(false); 2802 m_transcodeAction->setEnabled(false); 2803 m_proxyAction->setEnabled(false); 2804 m_reloadAction->setEnabled(false); 2805 m_replaceAction->setEnabled(false); 2806 m_replaceInTimelineAction->setEnabled(false); 2807 m_locateAction->setEnabled(false); 2808 m_duplicateAction->setEnabled(false); 2809 m_openAction->setEnabled(false); 2810 m_deleteAction->setEnabled(false); 2811 m_renameAction->setEnabled(false); 2812 updateClipsCount(); 2813 } 2814 2815 std::vector<QString> Bin::selectedClipsIds(bool allowSubClips) 2816 { 2817 const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); 2818 std::vector<QString> ids; 2819 for (const QModelIndex &ix : indexes) { 2820 if (!ix.isValid() || ix.column() != 0) { 2821 continue; 2822 } 2823 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 2824 if (item->itemType() == AbstractProjectItem::SubClipItem) { 2825 if (allowSubClips) { 2826 auto subClipItem = std::static_pointer_cast<ProjectSubClip>(item); 2827 ids.push_back(subClipItem->cutClipId()); 2828 } else { 2829 ids.push_back(item->clipId()); 2830 } 2831 } else if (item->itemType() == AbstractProjectItem::ClipItem) { 2832 ids.push_back(item->clipId()); 2833 } 2834 } 2835 return ids; 2836 } 2837 2838 QList<std::shared_ptr<ProjectClip>> Bin::selectedClips() 2839 { 2840 auto ids = selectedClipsIds(); 2841 QList<std::shared_ptr<ProjectClip>> ret; 2842 for (const auto &id : ids) { 2843 ret.push_back(m_itemModel->getClipByBinID(id)); 2844 } 2845 return ret; 2846 } 2847 2848 void Bin::slotInitView(QAction *action) 2849 { 2850 QString rootFolder; 2851 QString selectedItem; 2852 QStringList selectedItems; 2853 if (action) { 2854 QModelIndex currentSelection; 2855 if (m_itemView && m_proxyModel) { 2856 // Check currently selected item 2857 QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); 2858 for (auto &ix : indexes) { 2859 if (!ix.isValid()) { 2860 continue; 2861 } 2862 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 2863 if (currentItem) { 2864 selectedItems << currentItem->clipId(); 2865 } 2866 } 2867 currentSelection = m_proxyModel->selectionModel()->currentIndex(); 2868 if (currentSelection.isValid()) { 2869 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(currentSelection)); 2870 if (currentItem) { 2871 selectedItem = currentItem->clipId(); 2872 std::shared_ptr<ProjectFolder> parentFolder = std::static_pointer_cast<ProjectFolder>(currentItem->getEnclosingFolder()); 2873 if (parentFolder) { 2874 rootFolder = parentFolder->clipId(); 2875 } 2876 } 2877 } 2878 } 2879 if (m_proxyModel) { 2880 m_proxyModel->selectionModel()->clearSelection(); 2881 } 2882 int viewType = action->data().toInt(); 2883 if (m_isMainBin) { 2884 KdenliveSettings::setBinMode(viewType); 2885 } 2886 if (viewType == m_listType) { 2887 return; 2888 } 2889 if (m_listType == BinTreeView) { 2890 // save current treeview state (column width) 2891 auto *view = static_cast<QTreeView *>(m_itemView); 2892 m_headerInfo = view->header()->saveState(); 2893 m_showDate->setEnabled(true); 2894 m_showDesc->setEnabled(true); 2895 m_showRating->setEnabled(true); 2896 m_upAction->setEnabled(false); 2897 } 2898 m_listType = static_cast<BinViewType>(viewType); 2899 } 2900 if (m_itemView) { 2901 delete m_itemView; 2902 } 2903 delete m_binTreeViewDelegate; 2904 delete m_binListViewDelegate; 2905 m_binTreeViewDelegate = nullptr; 2906 m_binListViewDelegate = nullptr; 2907 2908 switch (m_listType) { 2909 case BinIconView: 2910 m_itemView = new MyListView(this); 2911 m_binListViewDelegate = new BinListItemDelegate(this); 2912 m_showDate->setEnabled(false); 2913 m_showDesc->setEnabled(false); 2914 m_showRating->setEnabled(false); 2915 m_upAction->setVisible(true); 2916 break; 2917 default: 2918 m_itemView = new MyTreeView(this); 2919 m_binTreeViewDelegate = new BinItemDelegate(this); 2920 m_showDate->setEnabled(true); 2921 m_showDesc->setEnabled(true); 2922 m_showRating->setEnabled(true); 2923 m_upAction->setVisible(false); 2924 break; 2925 } 2926 m_itemView->setMouseTracking(true); 2927 m_itemView->viewport()->installEventFilter(this); 2928 m_iconSize = m_baseIconSize * ((m_listType == BinIconView ? qMax(1, m_slider->value()) : m_slider->value()) / 4.0); 2929 m_itemView->setIconSize(m_iconSize); 2930 QPixmap pix(m_iconSize); 2931 pix.fill(Qt::lightGray); 2932 m_blankThumb.addPixmap(pix); 2933 m_itemView->addAction(m_renameAction); 2934 m_renameAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 2935 m_proxyModel = std::make_unique<ProjectSortProxyModel>(this); 2936 // Connect models 2937 m_proxyModel->setSourceModel(m_itemModel.get()); 2938 connect(m_itemModel.get(), &QAbstractItemModel::dataChanged, m_proxyModel.get(), &ProjectSortProxyModel::slotDataChanged); 2939 connect(m_proxyModel.get(), &ProjectSortProxyModel::updateRating, this, [&](const QModelIndex &ix, uint rating) { 2940 const QModelIndex index = m_proxyModel->mapToSource(ix); 2941 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(index); 2942 if (item) { 2943 uint previousRating = item->rating(); 2944 Fun undo = [this, item, index, previousRating]() { 2945 item->setRating(previousRating); 2946 Q_EMIT m_itemModel->dataChanged(index, index, {AbstractProjectItem::DataRating}); 2947 return true; 2948 }; 2949 Fun redo = [this, item, index, rating]() { 2950 item->setRating(rating); 2951 Q_EMIT m_itemModel->dataChanged(index, index, {AbstractProjectItem::DataRating}); 2952 return true; 2953 }; 2954 redo(); 2955 pCore->pushUndo(undo, redo, i18n("Edit rating")); 2956 } else { 2957 Q_EMIT displayBinMessage(i18n("Cannot set rating on this item"), KMessageWidget::Information); 2958 } 2959 }); 2960 connect(m_proxyModel.get(), &ProjectSortProxyModel::selectModel, this, &Bin::selectProxyModel); 2961 connect(m_proxyModel.get(), &QAbstractItemModel::layoutAboutToBeChanged, this, &Bin::slotSetSorting); 2962 m_itemView->setModel(m_proxyModel.get()); 2963 m_itemView->setSelectionModel(m_proxyModel->selectionModel()); 2964 m_proxyModel->setDynamicSortFilter(true); 2965 m_layout->insertWidget(2, m_itemView); 2966 // Reset drag type to normal 2967 m_itemModel->setDragType(PlaylistState::Disabled); 2968 2969 // setup some default view specific parameters 2970 if (m_listType == BinTreeView) { 2971 m_itemView->setItemDelegate(m_binTreeViewDelegate); 2972 auto *view = static_cast<MyTreeView *>(m_itemView); 2973 view->setSortingEnabled(true); 2974 view->setWordWrap(true); 2975 connect(view, &MyTreeView::updateDragMode, m_itemModel.get(), &ProjectItemModel::setDragType, Qt::DirectConnection); 2976 connect(view, &MyTreeView::processDragEnd, this, &Bin::processDragEnd); 2977 connect(view, &MyTreeView::selectCurrent, this, &Bin::ensureCurrent); 2978 connect(view, &MyTreeView::displayBinFrame, this, &Bin::showBinFrame); 2979 if (!m_headerInfo.isEmpty()) { 2980 view->header()->restoreState(m_headerInfo); 2981 } else { 2982 view->header()->resizeSections(QHeaderView::ResizeToContents); 2983 view->resizeColumnToContents(0); 2984 // Date Column 2985 view->setColumnHidden(1, true); 2986 // Description Column 2987 view->setColumnHidden(2, true); 2988 // Rating column 2989 view->setColumnHidden(7, true); 2990 } 2991 // Type column 2992 view->setColumnHidden(3, true); 2993 // Tags column 2994 view->setColumnHidden(4, true); 2995 // Duration column 2996 view->setColumnHidden(5, true); 2997 // ID column 2998 view->setColumnHidden(6, true); 2999 // Rating column 3000 view->header()->resizeSection(7, QFontInfo(font()).pixelSize() * 4); 3001 // Usage column 3002 view->setColumnHidden(8, true); 3003 m_showDate->setChecked(!view->isColumnHidden(1)); 3004 m_showDesc->setChecked(!view->isColumnHidden(2)); 3005 m_showRating->setChecked(!view->isColumnHidden(7)); 3006 if (m_sortGroup->checkedAction()) { 3007 view->header()->setSortIndicator(m_sortGroup->checkedAction()->data().toInt(), 3008 m_sortDescend->isChecked() ? Qt::DescendingOrder : Qt::AscendingOrder); 3009 } 3010 connect(view->header(), &QHeaderView::sectionResized, this, &Bin::slotSaveHeaders); 3011 connect(view->header(), &QHeaderView::sectionClicked, this, &Bin::slotSaveHeaders); 3012 connect(view->header(), &QHeaderView::sortIndicatorChanged, this, [this](int ix, Qt::SortOrder order) { 3013 QSignalBlocker bk(m_sortDescend); 3014 QSignalBlocker bk2(m_sortGroup); 3015 m_sortDescend->setChecked(order == Qt::DescendingOrder); 3016 QList<QAction *> actions = m_sortGroup->actions(); 3017 for (auto ac : qAsConst(actions)) { 3018 if (ac->data().toInt() == ix) { 3019 ac->setChecked(true); 3020 break; 3021 } 3022 } 3023 KdenliveSettings::setBinSorting(ix + (order == Qt::DescendingOrder ? 100 : 0)); 3024 }); 3025 connect(view, &MyTreeView::focusView, this, &Bin::slotGotFocus); 3026 } else if (m_listType == BinIconView) { 3027 m_itemView->setItemDelegate(m_binListViewDelegate); 3028 auto *view = static_cast<MyListView *>(m_itemView); 3029 connect(view, &MyListView::updateDragMode, m_itemModel.get(), &ProjectItemModel::setDragType, Qt::DirectConnection); 3030 int textHeight = int(QFontInfo(font()).pixelSize() * 1.5); 3031 view->setGridSize(QSize(m_iconSize.width() + 2, m_iconSize.height() + textHeight)); 3032 connect(view, &MyListView::focusView, this, &Bin::slotGotFocus); 3033 connect(view, &MyListView::displayBinFrame, this, &Bin::showBinFrame); 3034 connect(view, &MyListView::processDragEnd, this, &Bin::processDragEnd); 3035 if (!rootFolder.isEmpty()) { 3036 // Open view in a specific folder 3037 std::shared_ptr<AbstractProjectItem> folder = m_itemModel->getItemByBinId(rootFolder); 3038 auto parentIx = m_itemModel->getIndexFromItem(folder); 3039 m_itemView->setRootIndex(m_proxyModel->mapFromSource(parentIx)); 3040 m_upAction->setEnabled(rootFolder != QLatin1String("-1")); 3041 } 3042 } 3043 if (!selectedItems.isEmpty()) { 3044 for (auto &item : selectedItems) { 3045 std::shared_ptr<AbstractProjectItem> clip = m_itemModel->getItemByBinId(item); 3046 QModelIndex ix = m_itemModel->getIndexFromItem(clip); 3047 int row = ix.row(); 3048 const QModelIndex id = m_itemModel->index(row, 0, ix.parent()); 3049 const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, ix.parent()); 3050 if (id.isValid() && id2.isValid()) { 3051 m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), 3052 QItemSelectionModel::Select); 3053 } 3054 } 3055 } 3056 m_itemView->setEditTriggers(QAbstractItemView::NoEditTriggers); // DoubleClicked); 3057 m_itemView->setSelectionMode(QAbstractItemView::ExtendedSelection); 3058 m_itemView->setDragDropMode(QAbstractItemView::DragDrop); 3059 m_itemView->setAlternatingRowColors(true); 3060 m_itemView->setFocus(); 3061 } 3062 3063 void Bin::slotSetIconSize(int size) 3064 { 3065 if (!m_itemView) { 3066 return; 3067 } 3068 KdenliveSettings::setBin_zoom(size); 3069 m_iconSize = m_baseIconSize * ((m_listType == BinIconView ? qMax(1, size) : size) / 4.0); 3070 m_itemView->setIconSize(m_iconSize); 3071 if (m_listType == BinIconView) { 3072 auto *view = static_cast<MyListView *>(m_itemView); 3073 int textHeight = int(QFontInfo(font()).pixelSize() * 1.5); 3074 view->setGridSize(QSize(m_iconSize.width() + 2, m_iconSize.height() + textHeight)); 3075 } 3076 QPixmap pix(m_iconSize); 3077 pix.fill(Qt::lightGray); 3078 m_blankThumb.addPixmap(pix); 3079 } 3080 3081 void Bin::contextMenuEvent(QContextMenuEvent *event) 3082 { 3083 bool enableClipActions = false; 3084 bool isFolder = false; 3085 QString clipService; 3086 bool clickInView = false; 3087 if (m_itemView) { 3088 QRect viewRect(m_itemView->mapToGlobal(QPoint(0, 0)), m_itemView->mapToGlobal(QPoint(m_itemView->width(), m_itemView->height()))); 3089 if (viewRect.contains(event->globalPos())) { 3090 clickInView = true; 3091 QModelIndex idx = m_itemView->indexAt(m_itemView->viewport()->mapFromGlobal(event->globalPos())); 3092 if (idx.isValid()) { 3093 // User right clicked on a clip 3094 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(idx)); 3095 if (currentItem) { 3096 enableClipActions = true; 3097 if (currentItem->itemType() == AbstractProjectItem::FolderItem) { 3098 isFolder = true; 3099 } 3100 } 3101 } 3102 } 3103 } 3104 if (!clickInView) { 3105 return; 3106 } 3107 3108 // New folder can be created from level of another folder. 3109 if (isFolder) { 3110 m_menu->insertAction(m_deleteAction, m_createFolderAction); 3111 m_menu->insertAction(m_createFolderAction, m_sequencesFolderAction); 3112 m_menu->insertAction(m_sequencesFolderAction, m_openInBin); 3113 } else { 3114 m_menu->removeAction(m_createFolderAction); 3115 m_menu->removeAction(m_openInBin); 3116 m_menu->removeAction(m_sequencesFolderAction); 3117 } 3118 3119 // Show menu 3120 event->setAccepted(true); 3121 if (enableClipActions) { 3122 m_menu->exec(event->globalPos()); 3123 } else { 3124 // Clicked in empty area, will show main menu. 3125 // Before that `createFolderAction` is inserted - it allows to distinguish between showing that item by clicking on empty area and by clicking on "Add 3126 // Clip" menu. 3127 m_addButton->menu()->insertAction(m_addClip, m_createFolderAction); 3128 3129 m_addButton->menu()->exec(event->globalPos()); 3130 3131 // Clear up this action to not show it on "Add Clip" menu from toolbar icon. 3132 m_addButton->menu()->removeAction(m_createFolderAction); 3133 } 3134 } 3135 3136 void Bin::slotItemDoubleClicked(const QModelIndex &ix, const QPoint &pos, uint modifiers) 3137 { 3138 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 3139 if (m_listType == BinIconView) { 3140 if (item->childCount() > 0 || item->itemType() == AbstractProjectItem::FolderItem) { 3141 m_itemView->setRootIndex(ix); 3142 parentWidget()->setWindowTitle(item->name()); 3143 m_upAction->setEnabled(true); 3144 return; 3145 } 3146 } else { 3147 if (!m_isMainBin && item->itemType() == AbstractProjectItem::FolderItem) { 3148 // Double click a folder in secondary bin will set it as bin root 3149 m_itemView->setRootIndex(ix); 3150 parentWidget()->setWindowTitle(item->name()); 3151 m_upAction->setEnabled(true); 3152 return; 3153 } 3154 if (ix.column() == 0 && item->childCount() > 0) { 3155 QRect IconRect = m_itemView->visualRect(ix); 3156 IconRect.setWidth(int(double(IconRect.height()) / m_itemView->iconSize().height() * m_itemView->iconSize().width())); 3157 if (!pos.isNull() && (IconRect.contains(pos) || pos.y() > (IconRect.y() + IconRect.height() / 2))) { 3158 auto *view = static_cast<QTreeView *>(m_itemView); 3159 bool expand = !view->isExpanded(ix); 3160 // Expand all items on shift + double click 3161 if (modifiers & Qt::ShiftModifier) { 3162 if (expand) { 3163 view->expandAll(); 3164 } else { 3165 view->collapseAll(); 3166 } 3167 } else { 3168 view->setExpanded(ix, expand); 3169 } 3170 return; 3171 } 3172 } 3173 } 3174 if (ix.isValid()) { 3175 QRect IconRect = m_itemView->visualRect(ix); 3176 IconRect.setWidth(int(double(IconRect.height()) / m_itemView->iconSize().height() * m_itemView->iconSize().width())); 3177 if (!pos.isNull() && ((ix.column() == 2 && item->itemType() == AbstractProjectItem::ClipItem) || 3178 (!IconRect.contains(pos) && pos.y() < (IconRect.y() + IconRect.height() / 2)))) { 3179 // User clicked outside icon, trigger rename 3180 m_itemView->edit(ix); 3181 return; 3182 } 3183 if (item->itemType() == AbstractProjectItem::ClipItem) { 3184 std::shared_ptr<ProjectClip> clip = std::static_pointer_cast<ProjectClip>(item); 3185 if (clip) { 3186 if (clip->clipType() == ClipType::Timeline) { 3187 const QUuid uuid = clip->getSequenceUuid(); 3188 pCore->projectManager()->openTimeline(clip->binId(), uuid); 3189 } else if (clip->clipType() == ClipType::Text || clip->clipType() == ClipType::TextTemplate) { 3190 // m_propertiesPanel->setEnabled(false); 3191 showTitleWidget(clip); 3192 } else { 3193 slotSwitchClipProperties(clip); 3194 } 3195 } 3196 } 3197 } 3198 } 3199 3200 void Bin::slotEditClip() 3201 { 3202 if (m_propertiesPanel == nullptr) { 3203 return; 3204 } 3205 QString panelId = m_propertiesPanel->property("clipId").toString(); 3206 QModelIndex current = m_proxyModel->selectionModel()->currentIndex(); 3207 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(current)); 3208 if (item->clipId() != panelId) { 3209 // wrong clip 3210 return; 3211 } 3212 auto clip = std::static_pointer_cast<ProjectClip>(item); 3213 QString parentFolder = getCurrentFolder(); 3214 switch (clip->clipType()) { 3215 case ClipType::Text: 3216 case ClipType::TextTemplate: 3217 showTitleWidget(clip); 3218 break; 3219 case ClipType::SlideShow: 3220 showSlideshowWidget(clip); 3221 break; 3222 case ClipType::QText: 3223 ClipCreationDialog::createQTextClip(parentFolder, this, clip.get()); 3224 break; 3225 default: 3226 break; 3227 } 3228 } 3229 3230 void Bin::slotSwitchClipProperties() 3231 { 3232 QModelIndex current = m_proxyModel->selectionModel()->currentIndex(); 3233 if (current.isValid()) { 3234 // User clicked in the icon, open clip properties 3235 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(current)); 3236 std::shared_ptr<ProjectClip> currentItem = nullptr; 3237 if (item->itemType() == AbstractProjectItem::ClipItem) { 3238 currentItem = std::static_pointer_cast<ProjectClip>(item); 3239 } else if (item->itemType() == AbstractProjectItem::SubClipItem) { 3240 currentItem = std::static_pointer_cast<ProjectSubClip>(item)->getMasterClip(); 3241 } 3242 if (currentItem) { 3243 slotSwitchClipProperties(currentItem); 3244 return; 3245 } 3246 } 3247 slotSwitchClipProperties(nullptr); 3248 } 3249 3250 void Bin::slotSwitchClipProperties(const std::shared_ptr<ProjectClip> &clip) 3251 { 3252 if (m_propertiesPanel == nullptr) { 3253 return; 3254 } 3255 if (clip == nullptr) { 3256 m_propertiesPanel->setEnabled(false); 3257 return; 3258 } 3259 if (clip->clipType() == ClipType::SlideShow) { 3260 m_propertiesPanel->setEnabled(false); 3261 showSlideshowWidget(clip); 3262 } else if (clip->clipType() == ClipType::QText) { 3263 m_propertiesPanel->setEnabled(false); 3264 QString parentFolder = getCurrentFolder(); 3265 ClipCreationDialog::createQTextClip(parentFolder, this, clip.get()); 3266 } else { 3267 m_propertiesPanel->setEnabled(true); 3268 Q_EMIT requestShowClipProperties(clip); 3269 m_propertiesDock->show(); 3270 m_propertiesDock->raise(); 3271 } 3272 // Check if properties panel is not tabbed under Bin 3273 // if (!pCore->window()->isTabbedWith(m_propertiesDock, QStringLiteral("project_bin"))) { 3274 } 3275 3276 void Bin::doRefreshPanel(const QString &id) 3277 { 3278 std::shared_ptr<ProjectClip> currentItem = getFirstSelectedClip(); 3279 if ((currentItem != nullptr) && currentItem->AbstractProjectItem::clipId() == id) { 3280 Q_EMIT requestShowClipProperties(currentItem, true); 3281 } 3282 } 3283 3284 QAction *Bin::addAction(const QString &name, const QString &text, const QIcon &icon, const QString &category) 3285 { 3286 auto *action = new QAction(text, this); 3287 if (!icon.isNull()) { 3288 action->setIcon(icon); 3289 } 3290 pCore->window()->addAction(name, action, {}, category); 3291 return action; 3292 } 3293 3294 void Bin::setupAddClipAction(QMenu *addClipMenu, ClipType::ProducerType type, const QString &name, const QString &text, const QIcon &icon) 3295 { 3296 QAction *action = addAction(name, text, icon, QStringLiteral("addclip")); 3297 action->setData(static_cast<QVariant>(type)); 3298 addClipMenu->addAction(action); 3299 connect(action, &QAction::triggered, this, &Bin::slotCreateProjectClip); 3300 if (name == QLatin1String("add_animation_clip") && !KdenliveSettings::producerslist().contains(QLatin1String("glaxnimate"))) { 3301 action->setEnabled(false); 3302 } else if (name == QLatin1String("add_text_clip") && !KdenliveSettings::producerslist().contains(QLatin1String("kdenlivetitle"))) { 3303 action->setEnabled(false); 3304 } 3305 } 3306 3307 void Bin::showClipProperties(const std::shared_ptr<ProjectClip> &clip, bool forceRefresh) 3308 { 3309 if (m_propertiesPanel == nullptr) { 3310 return; 3311 } 3312 if ((clip == nullptr) || !clip->statusReady()) { 3313 for (QWidget *w : m_propertiesPanel->findChildren<ClipPropertiesController *>()) { 3314 delete w; 3315 } 3316 m_propertiesPanel->setProperty("clipId", QString()); 3317 m_propertiesPanel->setEnabled(false); 3318 Q_EMIT setupTargets(false, {}); 3319 return; 3320 } 3321 m_propertiesPanel->setEnabled(true); 3322 QString panelId = m_propertiesPanel->property("clipId").toString(); 3323 if (!forceRefresh && panelId == clip->AbstractProjectItem::clipId()) { 3324 // the properties panel is already displaying current clip, do nothing 3325 return; 3326 } 3327 // Cleanup widget for new content 3328 for (QWidget *w : m_propertiesPanel->findChildren<ClipPropertiesController *>()) { 3329 delete w; 3330 } 3331 m_propertiesPanel->setProperty("clipId", clip->AbstractProjectItem::clipId()); 3332 // Setup timeline targets 3333 Q_EMIT setupTargets(clip->hasVideo(), clip->activeStreams()); 3334 auto *lay = static_cast<QVBoxLayout *>(m_propertiesPanel->layout()); 3335 if (lay == nullptr) { 3336 lay = new QVBoxLayout(m_propertiesPanel); 3337 m_propertiesPanel->setLayout(lay); 3338 } 3339 ClipPropertiesController *panel = clip->buildProperties(m_propertiesPanel); 3340 connect(panel, &ClipPropertiesController::updateClipProperties, this, &Bin::slotEditClipCommand); 3341 connect(panel, &ClipPropertiesController::seekToFrame, m_monitor, static_cast<void (Monitor::*)(int)>(&Monitor::slotSeek)); 3342 connect(panel, &ClipPropertiesController::editClip, this, &Bin::slotEditClip); 3343 connect(panel, &ClipPropertiesController::editAnalysis, this, &Bin::slotAddClipExtraData); 3344 3345 lay->addWidget(panel); 3346 } 3347 3348 void Bin::slotEditClipCommand(const QString &id, const QMap<QString, QString> &oldProps, const QMap<QString, QString> &newProps) 3349 { 3350 auto *command = new EditClipCommand(this, id, oldProps, newProps, true); 3351 m_doc->commandStack()->push(command); 3352 } 3353 3354 void Bin::reloadClip(const QString &id) 3355 { 3356 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id); 3357 if (!clip) { 3358 return; 3359 } 3360 clip->reloadProducer(false, false); 3361 } 3362 3363 void Bin::reloadMonitorIfActive(const QString &id) 3364 { 3365 if (m_monitor->activeClipId() == id || m_monitor->activeClipId().isEmpty()) { 3366 slotOpenCurrent(); 3367 if (m_monitor->activeClipId() == id) { 3368 // Ensure proxy action is correctly updated 3369 if (m_doc->useProxy()) { 3370 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getItemByBinId(id); 3371 if (item) { 3372 AbstractProjectItem::PROJECTITEMTYPE itemType = item->itemType(); 3373 if (itemType == AbstractProjectItem::ClipItem) { 3374 std::shared_ptr<ProjectClip> clip = std::static_pointer_cast<ProjectClip>(item); 3375 if (clip && clip->statusReady()) { 3376 m_proxyAction->blockSignals(true); 3377 m_proxyAction->setChecked(clip->hasProxy()); 3378 m_proxyAction->blockSignals(false); 3379 } 3380 } 3381 } 3382 } 3383 Q_EMIT requestShowClipProperties(getBinClip(id), true); 3384 } 3385 } 3386 } 3387 3388 void Bin::reloadMonitorStreamIfActive(const QString &id) 3389 { 3390 if (m_monitor->activeClipId() == id) { 3391 m_monitor->reloadActiveStream(); 3392 } 3393 } 3394 3395 void Bin::updateTargets(QString id) 3396 { 3397 if (id == QLatin1String("-1")) { 3398 id = m_monitor->activeClipId(); 3399 } else if (m_monitor->activeClipId() != id) { 3400 qDebug() << "ABOIRT A"; 3401 return; 3402 } 3403 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id); 3404 if (clip) { 3405 Q_EMIT setupTargets(clip->hasVideo(), clip->activeStreams()); 3406 } else { 3407 Q_EMIT setupTargets(false, {}); 3408 } 3409 } 3410 3411 QStringList Bin::getBinFolderClipIds(const QString &id) const 3412 { 3413 QStringList ids; 3414 std::shared_ptr<ProjectFolder> folder = m_itemModel->getFolderByBinId(id); 3415 if (folder) { 3416 for (int i = 0; i < folder->childCount(); i++) { 3417 std::shared_ptr<AbstractProjectItem> child = std::static_pointer_cast<AbstractProjectItem>(folder->child(i)); 3418 if (child->itemType() == AbstractProjectItem::ClipItem) { 3419 ids << child->clipId(); 3420 } 3421 } 3422 } 3423 return ids; 3424 } 3425 3426 std::shared_ptr<ProjectClip> Bin::getBinClip(const QString &id) 3427 { 3428 return m_itemModel->getClipByBinID(id); 3429 } 3430 3431 const QString Bin::getBinClipName(const QString &id) const 3432 { 3433 std::shared_ptr<ProjectClip> clip = nullptr; 3434 if (id.contains(QLatin1Char('_'))) { 3435 clip = m_itemModel->getClipByBinID(id.section(QLatin1Char('_'), 0, 0)); 3436 } else if (!id.isEmpty()) { 3437 clip = m_itemModel->getClipByBinID(id); 3438 } 3439 if (clip) { 3440 return clip->clipName(); 3441 } 3442 return QString(); 3443 } 3444 3445 void Bin::setWaitingStatus(const QString &id) 3446 { 3447 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id); 3448 if (clip) { 3449 clip->setClipStatus(FileStatus::StatusWaiting); 3450 } 3451 } 3452 3453 void Bin::slotRemoveInvalidClip(const QString &id, bool replace, const QString &errorMessage) 3454 { 3455 Q_UNUSED(replace); 3456 3457 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id); 3458 if (!clip) { 3459 return; 3460 } 3461 Q_EMIT requesteInvalidRemoval(id, clip->url(), errorMessage); 3462 } 3463 3464 void Bin::selectClip(const std::shared_ptr<ProjectClip> &clip) 3465 { 3466 QModelIndex ix = m_itemModel->getIndexFromItem(clip); 3467 int row = ix.row(); 3468 const QModelIndex id = m_itemModel->index(row, 0, ix.parent()); 3469 // Ensure parent folder is expanded 3470 if (m_listType == BinTreeView) { 3471 // Make sure parent folder is expanded 3472 auto *view = static_cast<QTreeView *>(m_itemView); 3473 view->expand(m_proxyModel->mapFromSource(ix.parent())); 3474 const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, ix.parent()); 3475 if (id.isValid() && id2.isValid()) { 3476 m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), 3477 QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current); 3478 } 3479 } else { 3480 // Ensure parent folder is currently opened 3481 m_itemView->setRootIndex(m_proxyModel->mapFromSource(ix.parent())); 3482 m_upAction->setEnabled(!ix.parent().data(AbstractProjectItem::DataId).toString().isEmpty()); 3483 if (id.isValid()) { 3484 m_proxyModel->selectionModel()->select(m_proxyModel->mapFromSource(id), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current); 3485 } 3486 } 3487 m_itemView->scrollTo(m_proxyModel->mapFromSource(ix), QAbstractItemView::EnsureVisible); 3488 } 3489 3490 void Bin::slotOpenCurrent() 3491 { 3492 std::shared_ptr<ProjectClip> currentItem = getFirstSelectedClip(); 3493 if (currentItem) { 3494 Q_EMIT openClip(currentItem); 3495 } 3496 } 3497 3498 void Bin::openProducer(std::shared_ptr<ProjectClip> controller) 3499 { 3500 Q_EMIT openClip(std::move(controller)); 3501 } 3502 3503 void Bin::openProducer(std::shared_ptr<ProjectClip> controller, int in, int out) 3504 { 3505 Q_EMIT openClip(std::move(controller), in, out); 3506 } 3507 3508 void Bin::emitItemUpdated(std::shared_ptr<AbstractProjectItem> item) 3509 { 3510 Q_EMIT itemUpdated(std::move(item)); 3511 } 3512 3513 void Bin::emitRefreshPanel(const QString &id) 3514 { 3515 Q_EMIT refreshPanel(id); 3516 } 3517 3518 void Bin::setupGeneratorMenu() 3519 { 3520 if (!m_menu) { 3521 qCDebug(KDENLIVE_LOG) << "Warning, menu was not created, something is wrong"; 3522 return; 3523 } 3524 3525 auto *addMenu = qobject_cast<QMenu *>(pCore->window()->factory()->container(QStringLiteral("generators"), pCore->window())); 3526 if (addMenu) { 3527 QMenu *menu = m_addButton->menu(); 3528 menu->addMenu(addMenu); 3529 addMenu->setEnabled(!addMenu->isEmpty()); 3530 m_addButton->setMenu(menu); 3531 } 3532 3533 addMenu = qobject_cast<QMenu *>(pCore->window()->factory()->container(QStringLiteral("extract_audio"), pCore->window())); 3534 if (addMenu) { 3535 m_menu->addMenu(addMenu); 3536 addMenu->setEnabled(!addMenu->isEmpty()); 3537 m_extractAudioAction = addMenu; 3538 m_extractAudioAction->setEnabled(false); 3539 } 3540 3541 addMenu = qobject_cast<QMenu *>(pCore->window()->factory()->container(QStringLiteral("clip_actions"), pCore->window())); 3542 if (addMenu) { 3543 m_menu->addMenu(addMenu); 3544 addMenu->setEnabled(!addMenu->isEmpty()); 3545 m_clipsActionsMenu = addMenu; 3546 m_clipsActionsMenu->setEnabled(false); 3547 } 3548 3549 addMenu = qobject_cast<QMenu *>(pCore->window()->factory()->container(QStringLiteral("clip_in_timeline"), pCore->window())); 3550 if (addMenu) { 3551 m_inTimelineAction = m_menu->addMenu(addMenu); 3552 } 3553 3554 if (m_locateAction) { 3555 m_menu->addAction(m_locateAction); 3556 } 3557 if (m_reloadAction) { 3558 m_menu->addAction(m_reloadAction); 3559 } 3560 if (m_replaceAction) { 3561 m_menu->addAction(m_replaceAction); 3562 } 3563 if (m_replaceInTimelineAction) { 3564 m_menu->addAction(m_replaceInTimelineAction); 3565 } 3566 if (m_duplicateAction) { 3567 m_menu->addAction(m_duplicateAction); 3568 } 3569 if (m_transcodeAction) { 3570 m_menu->addAction(m_transcodeAction); 3571 } 3572 if (m_proxyAction) { 3573 m_menu->addAction(m_proxyAction); 3574 } 3575 3576 addMenu = qobject_cast<QMenu *>(pCore->window()->factory()->container(QStringLiteral("clip_timeline"), pCore->window())); 3577 if (addMenu) { 3578 m_menu->addMenu(addMenu); 3579 addMenu->setEnabled(false); 3580 } 3581 m_menu->addAction(m_editAction); 3582 m_menu->addAction(m_openAction); 3583 m_menu->addAction(m_renameAction); 3584 m_menu->addAction(m_deleteAction); 3585 m_menu->insertSeparator(m_deleteAction); 3586 } 3587 3588 void Bin::setupMenu() 3589 { 3590 auto *addClipMenu = new QMenu(this); 3591 3592 m_addClip = 3593 addAction(QStringLiteral("add_clip"), i18n("Add Clip or Folder…"), QIcon::fromTheme(QStringLiteral("kdenlive-add-clip")), QStringLiteral("addclip")); 3594 m_addClip->setWhatsThis(xi18nc("@info:whatsthis", "Main dialog to add source material to your project bin (videos, images, audio, titles, animations).<nl/>" 3595 "Click on the down-arrow icon to get a list of source types to select from.<nl/>" 3596 "Click on the media icon to open a window to select source files.")); 3597 addClipMenu->addAction(m_addClip); 3598 connect(m_addClip, &QAction::triggered, this, &Bin::slotAddClip); 3599 3600 setupAddClipAction(addClipMenu, ClipType::Color, QStringLiteral("add_color_clip"), i18n("Add Color Clip…"), 3601 QIcon::fromTheme(QStringLiteral("kdenlive-add-color-clip"))); 3602 setupAddClipAction(addClipMenu, ClipType::SlideShow, QStringLiteral("add_slide_clip"), i18n("Add Image Sequence…"), 3603 QIcon::fromTheme(QStringLiteral("kdenlive-add-slide-clip"))); 3604 setupAddClipAction(addClipMenu, ClipType::Text, QStringLiteral("add_text_clip"), i18n("Add Title Clip…"), 3605 QIcon::fromTheme(QStringLiteral("kdenlive-add-text-clip"))); 3606 setupAddClipAction(addClipMenu, ClipType::TextTemplate, QStringLiteral("add_text_template_clip"), i18n("Add Template Title…"), 3607 QIcon::fromTheme(QStringLiteral("kdenlive-add-text-clip"))); 3608 setupAddClipAction(addClipMenu, ClipType::Animation, QStringLiteral("add_animation_clip"), i18n("Create Animation…"), 3609 QIcon::fromTheme(QStringLiteral("motion_path_animations"))); 3610 setupAddClipAction(addClipMenu, ClipType::Timeline, QStringLiteral("add_playlist_clip"), i18n("Add Sequence…"), 3611 QIcon::fromTheme(QStringLiteral("list-add"))); 3612 QAction *downloadResourceAction = 3613 addAction(QStringLiteral("download_resource"), i18n("Online Resources"), QIcon::fromTheme(QStringLiteral("edit-download"))); 3614 addClipMenu->addAction(downloadResourceAction); 3615 connect(downloadResourceAction, &QAction::triggered, pCore->window(), &MainWindow::slotDownloadResources); 3616 3617 m_locateAction = addAction(QStringLiteral("locate_clip"), i18n("Locate Clip…"), QIcon::fromTheme(QStringLiteral("find-location"))); 3618 m_locateAction->setData("locate_clip"); 3619 m_locateAction->setEnabled(false); 3620 connect(m_locateAction, &QAction::triggered, this, &Bin::slotLocateClip); 3621 3622 m_reloadAction = addAction(QStringLiteral("reload_clip"), i18n("Reload Clip"), QIcon::fromTheme(QStringLiteral("view-refresh"))); 3623 m_reloadAction->setData("reload_clip"); 3624 m_reloadAction->setEnabled(false); 3625 connect(m_reloadAction, &QAction::triggered, this, &Bin::slotReloadClip); 3626 3627 m_transcodeAction = 3628 addAction(QStringLiteral("friendly_transcoder"), i18n("Transcode to Edit Friendly Format…"), QIcon::fromTheme(QStringLiteral("edit-copy"))); 3629 m_transcodeAction->setData("transcode_clip"); 3630 m_transcodeAction->setEnabled(false); 3631 connect(m_transcodeAction, &QAction::triggered, this, &Bin::requestSelectionTranscoding); 3632 3633 m_replaceAction = addAction(QStringLiteral("replace_clip"), i18n("Replace Clip…"), QIcon::fromTheme(QStringLiteral("edit-find-replace"))); 3634 m_replaceAction->setData("replace_clip"); 3635 m_replaceAction->setEnabled(false); 3636 connect(m_replaceAction, &QAction::triggered, this, &Bin::slotReplaceClip); 3637 3638 m_replaceInTimelineAction = 3639 addAction(QStringLiteral("replace_in_timeline"), i18n("Replace Clip In Timeline…"), QIcon::fromTheme(QStringLiteral("edit-find-replace"))); 3640 m_replaceInTimelineAction->setData("replace_timeline_clip"); 3641 m_replaceInTimelineAction->setEnabled(false); 3642 connect(m_replaceInTimelineAction, &QAction::triggered, this, &Bin::slotReplaceClipInTimeline); 3643 3644 m_duplicateAction = addAction(QStringLiteral("duplicate_clip"), i18n("Duplicate Clip"), QIcon::fromTheme(QStringLiteral("edit-copy"))); 3645 m_duplicateAction->setData("duplicate_clip"); 3646 m_duplicateAction->setEnabled(false); 3647 connect(m_duplicateAction, &QAction::triggered, this, &Bin::slotDuplicateClip); 3648 3649 m_proxyAction = new QAction(i18n("Proxy Clip"), pCore->window()); 3650 pCore->window()->addAction(QStringLiteral("proxy_clip"), m_proxyAction); 3651 m_proxyAction->setData(QStringList() << QString::number(static_cast<int>(AbstractTask::PROXYJOB))); 3652 m_proxyAction->setCheckable(true); 3653 m_proxyAction->setChecked(false); 3654 m_proxyAction->setEnabled(false); 3655 3656 m_editAction = addAction(QStringLiteral("clip_properties"), i18n("Clip Properties"), QIcon::fromTheme(QStringLiteral("document-edit"))); 3657 m_editAction->setData("clip_properties"); 3658 m_editAction->setEnabled(false); 3659 connect(m_editAction, &QAction::triggered, this, static_cast<void (Bin::*)()>(&Bin::slotSwitchClipProperties)); 3660 3661 m_openAction = addAction(QStringLiteral("edit_clip"), i18n("Edit Clip"), QIcon::fromTheme(QStringLiteral("document-open"))); 3662 m_openAction->setData("edit_clip"); 3663 m_openAction->setEnabled(false); 3664 connect(m_openAction, &QAction::triggered, this, &Bin::slotOpenClipExtern); 3665 3666 m_renameAction = KStandardAction::renameFile(this, SLOT(slotRenameItem()), this); 3667 m_renameAction->setEnabled(false); 3668 if (m_itemView) { 3669 m_itemView->addAction(m_renameAction); 3670 m_renameAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 3671 } 3672 3673 m_deleteAction = addAction(QStringLiteral("delete_clip"), i18n("Delete Clip"), QIcon::fromTheme(QStringLiteral("edit-delete"))); 3674 m_deleteAction->setData("delete_clip"); 3675 m_deleteAction->setEnabled(false); 3676 connect(m_deleteAction, &QAction::triggered, this, &Bin::slotDeleteClip); 3677 3678 m_openInBin = addAction(QStringLiteral("add_bin"), i18n("Open in new bin"), QIcon::fromTheme(QStringLiteral("document-open"))); 3679 connect(m_openInBin, &QAction::triggered, this, [&]() { 3680 QModelIndex ix = m_proxyModel->selectionModel()->currentIndex(); 3681 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 3682 QString id; 3683 if (currentItem) { 3684 id = currentItem->clipId(); 3685 } 3686 pCore.get()->addBin(id); 3687 }); 3688 3689 m_sequencesFolderAction = 3690 addAction(QStringLiteral("sequence_folder"), i18n("Default Target Folder for Sequences"), QIcon::fromTheme(QStringLiteral("favorite"))); 3691 m_sequencesFolderAction->setCheckable(true); 3692 connect(m_sequencesFolderAction, &QAction::triggered, this, &Bin::setDefaultSequenceFolder); 3693 3694 m_createFolderAction = addAction(QStringLiteral("create_folder"), i18n("Create Folder"), QIcon::fromTheme(QStringLiteral("folder-new"))); 3695 m_createFolderAction->setWhatsThis( 3696 xi18nc("@info:whatsthis", 3697 "Creates a folder in the current position in the project bin. Allows for better organization of source files. Folders can be nested.")); 3698 connect(m_createFolderAction, &QAction::triggered, this, &Bin::slotAddFolder); 3699 3700 m_upAction = KStandardAction::up(this, SLOT(slotBack()), pCore->window()->actionCollection()); 3701 3702 // Setup actions 3703 QAction *first = m_toolbar->actions().at(0); 3704 m_toolbar->insertAction(first, m_deleteAction); 3705 m_toolbar->insertAction(m_deleteAction, m_createFolderAction); 3706 m_toolbar->insertAction(m_createFolderAction, m_upAction); 3707 3708 auto *m = new QMenu(this); 3709 m->addActions(addClipMenu->actions()); 3710 m_addButton = new QToolButton(this); 3711 m_addButton->setMenu(m); 3712 m_addButton->setDefaultAction(m_addClip); 3713 m_addButton->setPopupMode(QToolButton::MenuButtonPopup); 3714 m_toolbar->insertWidget(m_upAction, m_addButton); 3715 m_menu = new QMenu(this); 3716 if (m_isMainBin) { 3717 m_propertiesDock = pCore->window()->addDock(i18n("Clip Properties"), QStringLiteral("clip_properties"), m_propertiesPanel); 3718 m_propertiesDock->close(); 3719 } 3720 connect(m_menu, &QMenu::aboutToShow, this, &Bin::updateTimelineOccurrences); 3721 } 3722 3723 const QString Bin::getDocumentProperty(const QString &key) 3724 { 3725 return m_doc->getDocumentProperty(key); 3726 } 3727 3728 void Bin::doDisplayMessage(const QString &text, KMessageWidget::MessageType type, const QList<QAction *> &actions, bool showCloseButton, 3729 BinMessage::BinCategory messageCategory) 3730 { 3731 // Remove existing actions if any 3732 QList<QAction *> acts = m_infoMessage->actions(); 3733 while (!acts.isEmpty()) { 3734 QAction *a = acts.takeFirst(); 3735 m_infoMessage->removeAction(a); 3736 delete a; 3737 } 3738 m_currentMessage = messageCategory; 3739 m_infoMessage->setText(text); 3740 m_infoMessage->setWordWrap(text.length() > 35); 3741 for (QAction *action : actions) { 3742 m_infoMessage->addAction(action); 3743 connect(action, &QAction::triggered, this, &Bin::slotMessageActionTriggered); 3744 } 3745 m_infoMessage->setCloseButtonVisible(showCloseButton || actions.isEmpty()); 3746 m_infoMessage->setMessageType(type); 3747 m_infoMessage->animatedShow(); 3748 } 3749 3750 void Bin::doDisplayMessage(const QString &text, KMessageWidget::MessageType type, const QString logInfo) 3751 { 3752 // Remove existing actions if any 3753 m_currentMessage = BinMessage::BinCategory::InformationMessage; 3754 QList<QAction *> acts = m_infoMessage->actions(); 3755 while (!acts.isEmpty()) { 3756 QAction *a = acts.takeFirst(); 3757 m_infoMessage->removeAction(a); 3758 delete a; 3759 } 3760 m_infoMessage->setText(text); 3761 m_infoMessage->setWordWrap(text.length() > 35); 3762 QAction *ac = new QAction(i18n("Show log"), this); 3763 m_infoMessage->addAction(ac); 3764 connect(ac, &QAction::triggered, this, [this, logInfo](bool) { 3765 KMessageBox::error(this, logInfo, i18n("Detailed log")); 3766 slotMessageActionTriggered(); 3767 }); 3768 m_infoMessage->setCloseButtonVisible(false); 3769 m_infoMessage->setMessageType(type); 3770 m_infoMessage->animatedShow(); 3771 } 3772 3773 void Bin::refreshClip(const QString &id) 3774 { 3775 if (m_monitor->activeClipId() == id) { 3776 if (pCore->monitorManager()->clipMonitorVisible()) { 3777 m_monitor->refreshMonitor(true); 3778 } 3779 } 3780 } 3781 3782 void Bin::slotCreateProjectClip() 3783 { 3784 auto *act = qobject_cast<QAction *>(sender()); 3785 if (act == nullptr) { 3786 // Cannot access triggering action, something is wrong 3787 qCDebug(KDENLIVE_LOG) << "// Error in clip creation action"; 3788 return; 3789 } 3790 ClipType::ProducerType type = ClipType::ProducerType(act->data().toInt()); 3791 QString parentFolder = getCurrentFolder(); 3792 switch (type) { 3793 case ClipType::Color: 3794 ClipCreationDialog::createColorClip(m_doc, parentFolder, m_itemModel); 3795 break; 3796 case ClipType::SlideShow: 3797 ClipCreationDialog::createSlideshowClip(m_doc, parentFolder, m_itemModel); 3798 break; 3799 case ClipType::Text: 3800 ClipCreationDialog::createTitleClip(m_doc, parentFolder, QString(), m_itemModel); 3801 break; 3802 case ClipType::TextTemplate: 3803 ClipCreationDialog::createTitleTemplateClip(m_doc, parentFolder, m_itemModel); 3804 break; 3805 case ClipType::QText: 3806 ClipCreationDialog::createQTextClip(parentFolder, this); 3807 break; 3808 case ClipType::Animation: 3809 ClipCreationDialog::createAnimationClip(m_doc, parentFolder); 3810 break; 3811 case ClipType::Timeline: 3812 buildSequenceClip(); 3813 break; 3814 default: 3815 break; 3816 } 3817 pCore->window()->raiseBin(); 3818 } 3819 3820 void Bin::buildSequenceClip(int aTracks, int vTracks) 3821 { 3822 QScopedPointer<QDialog> dia(new QDialog(this)); 3823 Ui::NewTimeline_UI dia_ui; 3824 dia_ui.setupUi(dia.data()); 3825 dia->setWindowTitle(i18nc("@title:window", "Create New Sequence")); 3826 int timelinesCount = pCore->projectManager()->getTimelinesCount() + 1; 3827 dia_ui.sequence_name->setText(i18n("Sequence %1", timelinesCount)); 3828 dia_ui.video_tracks->setValue(vTracks == -1 ? KdenliveSettings::videotracks() : vTracks); 3829 dia_ui.audio_tracks->setValue(aTracks == -1 ? KdenliveSettings::audiotracks() : aTracks); 3830 dia_ui.sequence_name->setFocus(); 3831 dia_ui.sequence_name->selectAll(); 3832 if (dia->exec() == QDialog::Accepted) { 3833 int videoTracks = dia_ui.video_tracks->value(); 3834 int audioTracks = dia_ui.audio_tracks->value(); 3835 QString parentFolder = getCurrentFolder(); 3836 if (m_itemModel->defaultSequencesFolder() > -1) { 3837 const QString sequenceFolder = QString::number(m_itemModel->defaultSequencesFolder()); 3838 std::shared_ptr<ProjectFolder> folderItem = m_itemModel->getFolderByBinId(sequenceFolder); 3839 if (folderItem) { 3840 parentFolder = sequenceFolder; 3841 } 3842 } 3843 ClipCreationDialog::createPlaylistClip(dia_ui.sequence_name->text(), {audioTracks, videoTracks}, parentFolder, m_itemModel); 3844 } 3845 } 3846 3847 const QString Bin::buildSequenceClipWithUndo(Fun &undo, Fun &redo, int aTracks, int vTracks, QString suggestedName) 3848 { 3849 QScopedPointer<QDialog> dia(new QDialog(this)); 3850 Ui::NewTimeline_UI dia_ui; 3851 dia_ui.setupUi(dia.data()); 3852 dia->setWindowTitle(i18nc("@title:window", "Create New Sequence")); 3853 if (suggestedName.isEmpty()) { 3854 int timelinesCount = pCore->projectManager()->getTimelinesCount() + 1; 3855 suggestedName = i18n("Sequence %1", timelinesCount); 3856 } 3857 dia_ui.sequence_name->setText(suggestedName); 3858 dia_ui.video_tracks->setValue(vTracks == -1 ? KdenliveSettings::videotracks() : vTracks); 3859 dia_ui.audio_tracks->setValue(aTracks == -1 ? KdenliveSettings::audiotracks() : aTracks); 3860 if (dia->exec() == QDialog::Accepted) { 3861 int videoTracks = dia_ui.video_tracks->value(); 3862 int audioTracks = dia_ui.audio_tracks->value(); 3863 QString parentFolder = getCurrentFolder(); 3864 if (m_itemModel->defaultSequencesFolder() > -1) { 3865 const QString sequenceFolder = QString::number(m_itemModel->defaultSequencesFolder()); 3866 std::shared_ptr<ProjectFolder> folderItem = m_itemModel->getFolderByBinId(sequenceFolder); 3867 if (folderItem) { 3868 parentFolder = sequenceFolder; 3869 } 3870 } 3871 return ClipCreator::createPlaylistClipWithUndo(dia_ui.sequence_name->text(), {audioTracks, videoTracks}, parentFolder, m_itemModel, undo, redo); 3872 } 3873 return QString(); 3874 } 3875 3876 void Bin::slotItemDropped(const QStringList ids, const QModelIndex parent) 3877 { 3878 std::shared_ptr<AbstractProjectItem> parentItem; 3879 if (parent.isValid()) { 3880 parentItem = m_itemModel->getBinItemByIndex(parent); 3881 parentItem = parentItem->getEnclosingFolder(false); 3882 } else { 3883 parentItem = m_itemModel->getRootFolder(); 3884 } 3885 auto *moveCommand = new QUndoCommand(); 3886 moveCommand->setText(i18np("Move Clip", "Move Clips", ids.count())); 3887 QStringList folderIds; 3888 for (const QString &id : ids) { 3889 if (id.contains(QLatin1Char('/'))) { 3890 // trying to move clip zone, not allowed. Ignore 3891 continue; 3892 } 3893 if (id.startsWith(QLatin1Char('#'))) { 3894 // moving a folder, keep it for later 3895 folderIds << id; 3896 continue; 3897 } 3898 std::shared_ptr<ProjectClip> currentItem = m_itemModel->getClipByBinID(id); 3899 if (!currentItem) { 3900 continue; 3901 } 3902 std::shared_ptr<AbstractProjectItem> currentParent = currentItem->parent(); 3903 if (currentParent != parentItem) { 3904 // Item was dropped on a different folder 3905 new MoveBinClipCommand(this, id, currentParent->clipId(), parentItem->clipId(), moveCommand); 3906 } 3907 } 3908 if (!folderIds.isEmpty()) { 3909 for (QString id : qAsConst(folderIds)) { 3910 id.remove(0, 1); 3911 std::shared_ptr<ProjectFolder> currentItem = m_itemModel->getFolderByBinId(id); 3912 if (!currentItem || currentItem == parentItem) { 3913 continue; 3914 } 3915 std::shared_ptr<AbstractProjectItem> currentParent = currentItem->parent(); 3916 if (currentParent != parentItem) { 3917 // Item was dropped on a different folder 3918 new MoveBinFolderCommand(this, id, currentParent->clipId(), parentItem->clipId(), moveCommand); 3919 } 3920 } 3921 } 3922 if (moveCommand->childCount() == 0) { 3923 pCore->displayMessage(i18n("No valid clip to insert"), MessageType::ErrorMessage, 500); 3924 } else { 3925 m_doc->commandStack()->push(moveCommand); 3926 } 3927 } 3928 3929 void Bin::slotAddEffect(std::vector<QString> ids, const QStringList &effectData) 3930 { 3931 if (ids.size() == 0) { 3932 // Apply effect to all selected clips 3933 ids = selectedClipsIds(); 3934 } 3935 if (ids.size() == 0) { 3936 pCore->displayMessage(i18n("Select a clip to apply an effect"), MessageType::ErrorMessage, 500); 3937 } 3938 for (auto &id : ids) { 3939 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id); 3940 if (clip) { 3941 if (effectData.count() == 5) { 3942 // Paste effect from another stack 3943 std::shared_ptr<EffectStackModel> sourceStack = 3944 pCore->getItemEffectStack(QUuid(effectData.at(4)), effectData.at(1).toInt(), effectData.at(2).toInt()); 3945 clip->copyEffect(sourceStack, effectData.at(3).toInt()); 3946 } else { 3947 clip->addEffect(effectData.constFirst()); 3948 } 3949 } 3950 } 3951 } 3952 3953 void Bin::slotEffectDropped(const QStringList &effectData, const QModelIndex &parent) 3954 { 3955 if (parent.isValid()) { 3956 std::shared_ptr<AbstractProjectItem> parentItem = m_itemModel->getBinItemByIndex(parent); 3957 if (parentItem->itemType() == AbstractProjectItem::FolderItem) { 3958 // effect not supported on folder items 3959 Q_EMIT displayBinMessage(i18n("Cannot apply effects on folders"), KMessageWidget::Information); 3960 return; 3961 } 3962 int row = 0; 3963 QModelIndex parentIndex; 3964 if (parentItem->itemType() == AbstractProjectItem::SubClipItem) { 3965 // effect only supported on clip items 3966 parentItem = std::static_pointer_cast<ProjectSubClip>(parentItem)->getMasterClip(); 3967 QModelIndex ix = m_itemModel->getIndexFromItem(parentItem); 3968 row = ix.row(); 3969 parentIndex = ix.parent(); 3970 } else if (parentItem->itemType() == AbstractProjectItem::ClipItem) { 3971 // effect only supported on clip items 3972 row = parent.row(); 3973 parentIndex = parent.parent(); 3974 } 3975 bool res = false; 3976 if (effectData.count() == 5) { 3977 // Paste effect from another stack 3978 std::shared_ptr<EffectStackModel> sourceStack = 3979 pCore->getItemEffectStack(QUuid(effectData.at(4)), effectData.at(1).toInt(), effectData.at(2).toInt()); 3980 res = std::static_pointer_cast<ProjectClip>(parentItem)->copyEffect(sourceStack, effectData.at(3).toInt()); 3981 } else { 3982 res = std::static_pointer_cast<ProjectClip>(parentItem)->addEffect(effectData.constFirst()); 3983 } 3984 if (!res) { 3985 pCore->displayMessage(i18n("Cannot add effect to clip"), MessageType::ErrorMessage); 3986 } else { 3987 m_proxyModel->selectionModel()->clearSelection(); 3988 const QModelIndex id = m_itemModel->index(row, 0, parentIndex); 3989 const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, parentIndex); 3990 if (id.isValid() && id2.isValid()) { 3991 m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), 3992 QItemSelectionModel::Select); 3993 } 3994 setCurrent(parentItem); 3995 } 3996 } 3997 } 3998 3999 void Bin::slotTagDropped(const QString &tag, const QModelIndex &parent) 4000 { 4001 if (parent.isValid()) { 4002 std::shared_ptr<AbstractProjectItem> parentItem = m_itemModel->getBinItemByIndex(parent); 4003 if (parentItem->itemType() == AbstractProjectItem::ClipItem || parentItem->itemType() == AbstractProjectItem::SubClipItem) { 4004 if (parentItem->itemType() == AbstractProjectItem::SubClipItem) { 4005 qDebug() << "TAG DROPPED ON CLIPZOINE\n\n!!!!!!!!!!!!!!!"; 4006 } 4007 // effect only supported on clip/subclip items 4008 QString currentTag = parentItem->tags(); 4009 QMap<QString, QString> oldProps; 4010 oldProps.insert(QStringLiteral("kdenlive:tags"), currentTag); 4011 QMap<QString, QString> newProps; 4012 if (currentTag.isEmpty()) { 4013 currentTag = tag; 4014 } else if (!currentTag.contains(tag)) { 4015 currentTag.append(QStringLiteral(";") + tag); 4016 } 4017 newProps.insert(QStringLiteral("kdenlive:tags"), currentTag); 4018 slotEditClipCommand(parentItem->clipId(), oldProps, newProps); 4019 return; 4020 } 4021 if (parentItem->itemType() == AbstractProjectItem::FolderItem) { 4022 QList<QString> allClips; 4023 QList<std::shared_ptr<ProjectClip>> children = std::static_pointer_cast<ProjectFolder>(parentItem)->childClips(); 4024 for (auto &clp : children) { 4025 allClips << clp->clipId(); 4026 } 4027 editTags(allClips, tag, true); 4028 return; 4029 } 4030 } 4031 pCore->displayMessage(i18n("Select a clip to add a tag"), MessageType::ErrorMessage); 4032 } 4033 4034 void Bin::switchTag(const QString &tag, bool add) 4035 { 4036 const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); 4037 if (indexes.isEmpty()) { 4038 pCore->displayMessage(i18n("Select a clip to add a tag"), MessageType::ErrorMessage); 4039 } 4040 // Check for folders 4041 QList<QString> allClips; 4042 for (const QModelIndex &ix : indexes) { 4043 if (!ix.isValid() || ix.column() != 0) { 4044 continue; 4045 } 4046 std::shared_ptr<AbstractProjectItem> parentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 4047 if (parentItem->itemType() == AbstractProjectItem::FolderItem) { 4048 QList<std::shared_ptr<ProjectClip>> children = std::static_pointer_cast<ProjectFolder>(parentItem)->childClips(); 4049 for (auto &clp : children) { 4050 allClips << clp->clipId(); 4051 } 4052 } else { 4053 allClips << parentItem->clipId(); 4054 } 4055 } 4056 editTags(allClips, tag, add); 4057 } 4058 4059 void Bin::updateTags(const QMap<int, QStringList> &previousTags, const QMap<int, QStringList> &tags) 4060 { 4061 Fun undo = [this, previousTags, tags]() { 4062 m_tagsWidget->rebuildTags(previousTags); 4063 rebuildFilters(previousTags.size()); 4064 pCore->updateProjectTags(tags.size(), previousTags); 4065 return true; 4066 }; 4067 Fun redo = [this, previousTags, tags]() { 4068 m_tagsWidget->rebuildTags(tags); 4069 rebuildFilters(tags.size()); 4070 pCore->updateProjectTags(previousTags.size(), tags); 4071 return true; 4072 }; 4073 // Check if some tags were removed 4074 QList<QStringList> previous = previousTags.values(); 4075 QList<QStringList> updated = tags.values(); 4076 QStringList deletedTags; 4077 QMap<QString, QString> modifiedTags; 4078 for (auto p : previous) { 4079 bool tagExists = false; 4080 for (auto n : updated) { 4081 if (n.first() == p.first()) { 4082 // Tag still exists 4083 if (n.at(1) != p.at(1)) { 4084 // Tag color has changed 4085 modifiedTags.insert(p.at(1), n.at(1)); 4086 } 4087 tagExists = true; 4088 break; 4089 } 4090 } 4091 if (!tagExists) { 4092 // Tag was removed 4093 deletedTags << p.at(1); 4094 } 4095 } 4096 if (!deletedTags.isEmpty()) { 4097 // Remove tag from clips 4098 for (auto &t : deletedTags) { 4099 // Find clips with the tag 4100 const QList<QString> clips = getAllClipsWithTag(t); 4101 Fun update_tags_redo = [this, clips, t]() { 4102 for (auto &cid : clips) { 4103 std::shared_ptr<ProjectClip> clip = getBinClip(cid); 4104 if (clip) { 4105 QString tags = clip->tags(); 4106 QStringList tagsList = tags.split(QLatin1Char(';')); 4107 tagsList.removeAll(t); 4108 QMap<QString, QString> props; 4109 props.insert(QStringLiteral("kdenlive:tags"), tagsList.join(QLatin1Char(';'))); 4110 slotUpdateClipProperties(cid, props, false); 4111 } 4112 } 4113 return true; 4114 }; 4115 Fun update_tags_undo = [this, clips, t]() { 4116 for (auto &cid : clips) { 4117 std::shared_ptr<ProjectClip> clip = getBinClip(cid); 4118 if (clip) { 4119 QString tags = clip->tags(); 4120 QStringList tagsList = tags.split(QLatin1Char(';')); 4121 if (!tagsList.contains(t)) { 4122 tagsList << t; 4123 } 4124 QMap<QString, QString> props; 4125 props.insert(QStringLiteral("kdenlive:tags"), tagsList.join(QLatin1Char(';'))); 4126 slotUpdateClipProperties(cid, props, false); 4127 } 4128 } 4129 return true; 4130 }; 4131 UPDATE_UNDO_REDO(update_tags_redo, update_tags_undo, undo, redo); 4132 } 4133 } 4134 if (!modifiedTags.isEmpty()) { 4135 // Replace tag in clips 4136 QMapIterator<QString, QString> i(modifiedTags); 4137 while (i.hasNext()) { 4138 // Find clips with the tag 4139 i.next(); 4140 const QList<QString> clips = getAllClipsWithTag(i.key()); 4141 Fun update_tags_redo = [this, clips, previous = i.key(), updated = i.value()]() { 4142 for (auto &cid : clips) { 4143 std::shared_ptr<ProjectClip> clip = getBinClip(cid); 4144 if (clip) { 4145 QString tags = clip->tags(); 4146 QStringList tagsList = tags.split(QLatin1Char(';')); 4147 tagsList.removeAll(previous); 4148 tagsList << updated; 4149 QMap<QString, QString> props; 4150 props.insert(QStringLiteral("kdenlive:tags"), tagsList.join(QLatin1Char(';'))); 4151 slotUpdateClipProperties(cid, props, false); 4152 } 4153 } 4154 return true; 4155 }; 4156 Fun update_tags_undo = [this, clips, previous = i.key(), updated = i.value()]() { 4157 for (auto &cid : clips) { 4158 std::shared_ptr<ProjectClip> clip = getBinClip(cid); 4159 if (clip) { 4160 QString tags = clip->tags(); 4161 QStringList tagsList = tags.split(QLatin1Char(';')); 4162 tagsList.removeAll(updated); 4163 if (!tagsList.contains(previous)) { 4164 tagsList << previous; 4165 } 4166 QMap<QString, QString> props; 4167 props.insert(QStringLiteral("kdenlive:tags"), tagsList.join(QLatin1Char(';'))); 4168 slotUpdateClipProperties(cid, props, false); 4169 } 4170 } 4171 return true; 4172 }; 4173 UPDATE_UNDO_REDO(update_tags_redo, update_tags_undo, undo, redo); 4174 } 4175 } 4176 redo(); 4177 pCore->pushUndo(undo, redo, i18n("Edit Tags")); 4178 } 4179 4180 const QList<QString> Bin::getAllClipsWithTag(const QString &tag) 4181 { 4182 QList<QString> list; 4183 QList<std::shared_ptr<ProjectClip>> allClipIds = m_itemModel->getRootFolder()->childClips(); 4184 for (const auto &clip : qAsConst(allClipIds)) { 4185 if (clip->tags().contains(tag)) { 4186 list << clip->clipId(); 4187 } 4188 } 4189 return list; 4190 } 4191 4192 void Bin::editTags(const QList<QString> &allClips, const QString &tag, bool add) 4193 { 4194 for (const QString &id : allClips) { 4195 std::shared_ptr<AbstractProjectItem> clip = m_itemModel->getItemByBinId(id); 4196 if (clip) { 4197 // effect only supported on clip/subclip items 4198 QString currentTag = clip->tags(); 4199 QMap<QString, QString> oldProps; 4200 oldProps.insert(QStringLiteral("kdenlive:tags"), currentTag); 4201 QMap<QString, QString> newProps; 4202 if (add) { 4203 if (currentTag.isEmpty()) { 4204 currentTag = tag; 4205 } else if (!currentTag.contains(tag)) { 4206 currentTag.append(QStringLiteral(";") + tag); 4207 } 4208 newProps.insert(QStringLiteral("kdenlive:tags"), currentTag); 4209 } else { 4210 QStringList tags = currentTag.split(QLatin1Char(';')); 4211 tags.removeAll(tag); 4212 newProps.insert(QStringLiteral("kdenlive:tags"), tags.join(QLatin1Char(';'))); 4213 } 4214 slotEditClipCommand(id, oldProps, newProps); 4215 } 4216 } 4217 } 4218 4219 void Bin::editMasterEffect(const std::shared_ptr<AbstractProjectItem> &clip) 4220 { 4221 if (m_gainedFocus) { 4222 // Widget just gained focus, updating stack is managed in the eventfilter event, not from item 4223 return; 4224 } 4225 if (clip) { 4226 if (clip->itemType() == AbstractProjectItem::ClipItem) { 4227 std::shared_ptr<ProjectClip> clp = std::static_pointer_cast<ProjectClip>(clip); 4228 Q_EMIT requestShowEffectStack(clp->clipName(), clp->m_effectStack, clp->getFrameSize(), false); 4229 return; 4230 } 4231 if (clip->itemType() == AbstractProjectItem::SubClipItem) { 4232 if (auto ptr = clip->parentItem().lock()) { 4233 std::shared_ptr<ProjectClip> clp = std::static_pointer_cast<ProjectClip>(ptr); 4234 Q_EMIT requestShowEffectStack(clp->clipName(), clp->m_effectStack, clp->getFrameSize(), false); 4235 } 4236 return; 4237 } 4238 } 4239 Q_EMIT requestShowEffectStack(QString(), nullptr, QSize(), false); 4240 } 4241 4242 void Bin::slotGotFocus() 4243 { 4244 m_gainedFocus = true; 4245 } 4246 4247 void Bin::doMoveClip(const QString &id, const QString &newParentId) 4248 { 4249 std::shared_ptr<ProjectClip> currentItem = m_itemModel->getClipByBinID(id); 4250 if (!currentItem) { 4251 return; 4252 } 4253 std::shared_ptr<AbstractProjectItem> currentParent = currentItem->parent(); 4254 std::shared_ptr<ProjectFolder> newParent = m_itemModel->getFolderByBinId(newParentId); 4255 currentItem->changeParent(newParent); 4256 } 4257 4258 void Bin::doMoveFolder(const QString &id, const QString &newParentId) 4259 { 4260 std::shared_ptr<ProjectFolder> currentItem = m_itemModel->getFolderByBinId(id); 4261 std::shared_ptr<AbstractProjectItem> currentParent = currentItem->parent(); 4262 std::shared_ptr<ProjectFolder> newParent = m_itemModel->getFolderByBinId(newParentId); 4263 currentParent->removeChild(currentItem); 4264 currentItem->changeParent(newParent); 4265 } 4266 4267 void Bin::droppedUrls(const QList<QUrl> &urls, const QString &folderInfo) 4268 { 4269 QModelIndex current; 4270 if (folderInfo.isEmpty()) { 4271 current = m_proxyModel->mapToSource(m_proxyModel->selectionModel()->currentIndex()); 4272 } else { 4273 // get index for folder 4274 std::shared_ptr<ProjectFolder> folder = m_itemModel->getFolderByBinId(folderInfo); 4275 if (!folder) { 4276 folder = m_itemModel->getRootFolder(); 4277 } 4278 current = m_itemModel->getIndexFromItem(folder); 4279 } 4280 slotUrlsDropped(urls, current); 4281 } 4282 4283 const QString Bin::slotAddClipToProject(const QUrl &url) 4284 { 4285 QList<QUrl> urls; 4286 urls << url; 4287 QModelIndex current = m_proxyModel->mapToSource(m_proxyModel->selectionModel()->currentIndex()); 4288 return slotUrlsDropped(urls, current); 4289 } 4290 4291 const QString Bin::slotUrlsDropped(const QList<QUrl> urls, const QModelIndex parent) 4292 { 4293 QString parentFolder = m_itemModel->getRootFolder()->clipId(); 4294 if (parent.isValid()) { 4295 // Check if drop occurred on a folder 4296 std::shared_ptr<AbstractProjectItem> parentItem = m_itemModel->getBinItemByIndex(parent); 4297 while (parentItem->itemType() != AbstractProjectItem::FolderItem) { 4298 parentItem = parentItem->parent(); 4299 } 4300 parentFolder = parentItem->clipId(); 4301 } 4302 const QString id = ClipCreator::createClipsFromList(urls, true, parentFolder, m_itemModel); 4303 if (!id.isEmpty()) { 4304 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getItemByBinId(id); 4305 if (item) { 4306 QModelIndex ix = m_itemModel->getIndexFromItem(item); 4307 m_itemView->scrollTo(m_proxyModel->mapFromSource(ix), QAbstractItemView::EnsureVisible); 4308 } 4309 } 4310 return id; 4311 } 4312 4313 void Bin::slotExpandUrl(const ItemInfo &info, const QString &url, QUndoCommand *command) 4314 { 4315 Q_UNUSED(info) 4316 Q_UNUSED(url) 4317 Q_UNUSED(command) 4318 // TODO reimplement this 4319 /* 4320 // Create folder to hold imported clips 4321 QString folderName = QFileInfo(url).fileName().section(QLatin1Char('.'), 0, 0); 4322 QString folderId = QString::number(getFreeFolderId()); 4323 new AddBinFolderCommand(this, folderId, folderName.isEmpty() ? i18n("Folder") : folderName, m_itemModel->getRootFolder()->clipId(), false, command); 4324 4325 // Parse playlist clips 4326 QDomDocument doc; 4327 QFile file(url); 4328 doc.setContent(&file, false); //TODO: use Xml::docContentFromFile 4329 file.close(); 4330 bool invalid = false; 4331 if (doc.documentElement().isNull()) { 4332 invalid = true; 4333 } 4334 QDomNodeList producers = doc.documentElement().elementsByTagName(QStringLiteral("producer")); 4335 QDomNodeList tracks = doc.documentElement().elementsByTagName(QStringLiteral("track")); 4336 if (invalid || producers.isEmpty()) { 4337 doDisplayMessage(i18n("Playlist clip %1 is invalid.", QFileInfo(url).fileName()), KMessageWidget::Warning); 4338 delete command; 4339 return; 4340 } 4341 if (tracks.count() > pCore->projectManager()->currentTimeline()->visibleTracksCount() + 1) { 4342 doDisplayMessage( 4343 i18n("Playlist clip %1 has too many tracks (%2) to be imported. Add new tracks to your project.", QFileInfo(url).fileName(), tracks.count()), 4344 KMessageWidget::Warning); 4345 delete command; 4346 return; 4347 } 4348 // Maps playlist producer IDs to (project) bin producer IDs. 4349 QMap<QString, QString> idMap; 4350 // Maps hash IDs to (project) first playlist producer instance ID. This is 4351 // necessary to detect duplicate producer serializations produced by MLT. 4352 // This covers, for instance, images and titles. 4353 QMap<QString, QString> hashToIdMap; 4354 QDir mltRoot(doc.documentElement().attribute(QStringLiteral("root"))); 4355 for (int i = 0; i < producers.count(); i++) { 4356 QDomElement prod = producers.at(i).toElement(); 4357 QString originalId = prod.attribute(QStringLiteral("id")); 4358 // track producer 4359 if (originalId.contains(QLatin1Char('_'))) { 4360 originalId = originalId.section(QLatin1Char('_'), 0, 0); 4361 } 4362 // slowmotion producer 4363 if (originalId.contains(QLatin1Char(':'))) { 4364 originalId = originalId.section(QLatin1Char(':'), 1, 1); 4365 } 4366 4367 // We already have seen and mapped this producer. 4368 if (idMap.contains(originalId)) { 4369 continue; 4370 } 4371 4372 // Check for duplicate producers, based on hash value of producer. 4373 // Be careful as to the kdenlive:file_hash! It is not unique for 4374 // title clips, nor color clips. Also not sure about image sequences. 4375 // So we use mlt service-specific hashes to identify duplicate producers. 4376 QString hash; 4377 QString mltService = Xml::getXmlProperty(prod, QStringLiteral("mlt_service")); 4378 if (mltService == QLatin1String("pixbuf") || mltService == QLatin1String("qimage") || mltService == QLatin1String("kdenlivetitle") || 4379 mltService == QLatin1String("color") || mltService == QLatin1String("colour")) { 4380 hash = mltService + QLatin1Char(':') + Xml::getXmlProperty(prod, QStringLiteral("kdenlive:clipname")) + QLatin1Char(':') + 4381 Xml::getXmlProperty(prod, QStringLiteral("kdenlive:folderid")) + QLatin1Char(':'); 4382 if (mltService == QLatin1String("kdenlivetitle")) { 4383 // Calculate hash based on title contents. 4384 hash.append( 4385 QString(QCryptographicHash::hash(Xml::getXmlProperty(prod, QStringLiteral("xmldata")).toUtf8(), QCryptographicHash::Md5).toHex())); 4386 } else if (mltService == QLatin1String("pixbuf") || mltService == QLatin1String("qimage") || mltService == QLatin1String("color") || 4387 mltService == QLatin1String("colour")) { 4388 hash.append(Xml::getXmlProperty(prod, QStringLiteral("resource"))); 4389 } 4390 4391 QString singletonId = hashToIdMap.value(hash, QString()); 4392 if (singletonId.length() != 0) { 4393 // map duplicate producer ID to single bin clip producer ID. 4394 qCDebug(KDENLIVE_LOG) << "found duplicate producer:" << hash << ", reusing newID:" << singletonId; 4395 idMap.insert(originalId, singletonId); 4396 continue; 4397 } 4398 } 4399 4400 // First occurrence of a producer, so allocate new bin clip producer ID. 4401 QString newId = QString::number(getFreeClipId()); 4402 idMap.insert(originalId, newId); 4403 qCDebug(KDENLIVE_LOG) << "originalId: " << originalId << ", newId: " << newId; 4404 4405 // Ensure to register new bin clip producer ID in hash hashmap for 4406 // those clips that MLT likes to serialize multiple times. This is 4407 // indicated by having a hash "value" unqual "". See also above. 4408 if (hash.length() != 0) { 4409 hashToIdMap.insert(hash, newId); 4410 } 4411 4412 // Add clip 4413 QDomElement clone = prod.cloneNode(true).toElement(); 4414 EffectsList::setProperty(clone, QStringLiteral("kdenlive:folderid"), folderId); 4415 // Do we have a producer that uses a resource property that contains a path? 4416 if (mltService == QLatin1String("avformat-novalidate") // av clip 4417 || mltService == QLatin1String("avformat") // av clip 4418 || mltService == QLatin1String("pixbuf") // image (sequence) clip 4419 || mltService == QLatin1String("qimage") // image (sequence) clip 4420 || mltService == QLatin1String("xml") // MLT playlist clip, someone likes recursion :) 4421 ) { 4422 // Make sure to correctly resolve relative resource paths based on 4423 // the playlist's root, not on this project's root 4424 QString resource = Xml::getXmlProperty(clone, QStringLiteral("resource")); 4425 if (QFileInfo(resource).isRelative()) { 4426 QFileInfo rootedResource(mltRoot, resource); 4427 qCDebug(KDENLIVE_LOG) << "fixed resource path for producer, newId:" << newId << "resource:" << rootedResource.absoluteFilePath(); 4428 EffectsList::setProperty(clone, QStringLiteral("resource"), rootedResource.absoluteFilePath()); 4429 } 4430 } 4431 4432 ClipCreationDialog::createClipsCommand(this, clone, newId, command); 4433 } 4434 pCore->projectManager()->currentTimeline()->importPlaylist(info, idMap, doc, command); 4435 */ 4436 } 4437 4438 void Bin::slotItemEdited(const QModelIndex &ix, const QModelIndex &, const QVector<int> &roles) 4439 { 4440 if (ix.isValid() && roles.contains(AbstractProjectItem::DataName)) { 4441 // Clip renamed 4442 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(ix); 4443 auto clip = std::static_pointer_cast<ProjectClip>(item); 4444 if (clip) { 4445 Q_EMIT clipNameChanged(clip->AbstractProjectItem::clipId().toInt(), clip->clipName()); 4446 } 4447 } 4448 } 4449 4450 void Bin::renameSubClipCommand(const QString &id, const QString &newName, const QString &oldName, int in, int out) 4451 { 4452 auto *command = new RenameBinSubClipCommand(this, id, newName, oldName, in, out); 4453 m_doc->commandStack()->push(command); 4454 } 4455 4456 void Bin::renameSubClip(const QString &id, const QString &newName, int in, int out) 4457 { 4458 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id); 4459 if (!clip) { 4460 return; 4461 } 4462 std::shared_ptr<ProjectSubClip> sub = clip->getSubClip(in, out); 4463 if (!sub) { 4464 return; 4465 } 4466 sub->setName(newName.isEmpty() ? i18n("Unnamed") : newName); 4467 clip->updateZones(); 4468 Q_EMIT itemUpdated(sub); 4469 } 4470 4471 void Bin::slotStartFilterJob(const ItemInfo &info, const QString &id, QMap<QString, QString> &filterParams, QMap<QString, QString> &consumerParams, 4472 QMap<QString, QString> &extraParams) 4473 { 4474 Q_UNUSED(info) 4475 Q_UNUSED(id) 4476 Q_UNUSED(filterParams) 4477 Q_UNUSED(consumerParams) 4478 Q_UNUSED(extraParams) 4479 // TODO refac 4480 /* 4481 std::shared_ptr<ProjectClip> clip = getBinClip(id); 4482 if (!clip) { 4483 return; 4484 } 4485 4486 QMap<QString, QString> producerParams = QMap<QString, QString>(); 4487 producerParams.insert(QStringLiteral("producer"), clip->url()); 4488 if (info.cropDuration != GenTime()) { 4489 producerParams.insert(QStringLiteral("in"), QString::number((int)info.cropStart.frames(pCore->getCurrentFps()))); 4490 producerParams.insert(QStringLiteral("out"), QString::number((int)(info.cropStart + info.cropDuration).frames(pCore->getCurrentFps()))); 4491 extraParams.insert(QStringLiteral("clipStartPos"), QString::number((int)info.startPos.frames(pCore->getCurrentFps()))); 4492 extraParams.insert(QStringLiteral("clipTrack"), QString::number(info.track)); 4493 } else { 4494 // We want to process whole clip 4495 producerParams.insert(QStringLiteral("in"), QString::number(0)); 4496 producerParams.insert(QStringLiteral("out"), QString::number(-1)); 4497 } 4498 */ 4499 } 4500 4501 void Bin::focusBinView() const 4502 { 4503 m_itemView->setFocus(); 4504 } 4505 4506 void Bin::slotOpenClipExtern() 4507 { 4508 std::shared_ptr<ProjectClip> clip = getFirstSelectedClip(); 4509 if (!clip) { 4510 return; 4511 } 4512 QString errorString; 4513 switch (clip->clipType()) { 4514 case ClipType::Text: 4515 case ClipType::TextTemplate: 4516 showTitleWidget(clip); 4517 break; 4518 case ClipType::Image: { 4519 if (KdenliveSettings::defaultimageapp().isEmpty()) { 4520 QUrl url = KUrlRequesterDialog::getUrl(QUrl(), this, i18n("Enter path for your image editing application")); 4521 if (!url.isEmpty()) { 4522 KdenliveSettings::setDefaultimageapp(url.toLocalFile()); 4523 KdenliveSettingsDialog *d = static_cast<KdenliveSettingsDialog *>(KConfigDialog::exists(QStringLiteral("settings"))); 4524 if (d) { 4525 d->updateExternalApps(); 4526 } 4527 } 4528 } 4529 if (!KdenliveSettings::defaultimageapp().isEmpty()) { 4530 errorString = pCore->openExternalApp(KdenliveSettings::defaultimageapp(), {clip->url()}); 4531 } else { 4532 KMessageBox::error(QApplication::activeWindow(), i18n("Please set a default application to open image files")); 4533 } 4534 } break; 4535 case ClipType::Audio: { 4536 if (KdenliveSettings::defaultaudioapp().isEmpty()) { 4537 QUrl url = KUrlRequesterDialog::getUrl(QUrl(), this, i18n("Enter path for your audio editing application")); 4538 if (!url.isEmpty()) { 4539 KdenliveSettings::setDefaultaudioapp(url.toLocalFile()); 4540 KdenliveSettingsDialog *d = static_cast<KdenliveSettingsDialog *>(KConfigDialog::exists(QStringLiteral("settings"))); 4541 if (d) { 4542 d->updateExternalApps(); 4543 } 4544 } 4545 } 4546 if (!KdenliveSettings::defaultaudioapp().isEmpty()) { 4547 errorString = pCore->openExternalApp(KdenliveSettings::defaultaudioapp(), {clip->url()}); 4548 } else { 4549 KMessageBox::error(QApplication::activeWindow(), i18n("Please set a default application to open audio files")); 4550 } 4551 } break; 4552 case ClipType::Animation: { 4553 GlaxnimateLauncher::instance().openFile(clip->url()); 4554 } break; 4555 default: 4556 break; 4557 } 4558 if (!errorString.isEmpty()) { 4559 KMessageBox::detailedError(QApplication::activeWindow(), i18n("Cannot open file %1", clip->url()), errorString); 4560 } 4561 } 4562 4563 /* 4564 void Bin::slotGotFilterJobResults(const QString &id, int startPos, int track, const stringMap &results, const stringMap &filterInfo) 4565 { 4566 if (filterInfo.contains(QStringLiteral("finalfilter"))) { 4567 if (filterInfo.contains(QStringLiteral("storedata"))) { 4568 // Store returned data as clip extra data 4569 std::shared_ptr<ProjectClip> clip = getBinClip(id); 4570 if (clip) { 4571 QString key = filterInfo.value(QStringLiteral("key")); 4572 QStringList newValue = clip->updatedAnalysisData(key, results.value(key), filterInfo.value(QStringLiteral("offset")).toInt()); 4573 slotAddClipExtraData(id, newValue.at(0), newValue.at(1)); 4574 } 4575 } 4576 if (startPos == -1) { 4577 // Processing bin clip 4578 std::shared_ptr<ProjectClip> currentItem = m_itemModel->getClipByBinID(id); 4579 if (!currentItem) { 4580 return; 4581 } 4582 std::shared_ptr<ClipController> ctl = std::static_pointer_cast<ClipController>(currentItem); 4583 EffectsList list = ctl->effectList(); 4584 QDomElement effect = list.effectById(filterInfo.value(QStringLiteral("finalfilter"))); 4585 QDomDocument doc; 4586 QDomElement e = doc.createElement(QStringLiteral("test")); 4587 doc.appendChild(e); 4588 e.appendChild(doc.importNode(effect, true)); 4589 if (!effect.isNull()) { 4590 QDomElement newEffect = effect.cloneNode().toElement(); 4591 QMap<QString, QString>::const_iterator i = results.constBegin(); 4592 while (i != results.constEnd()) { 4593 EffectsList::setParameter(newEffect, i.key(), i.value()); 4594 ++i; 4595 } 4596 ctl->updateEffect(newEffect, effect.attribute(QStringLiteral("kdenlive_ix")).toInt()); 4597 Q_EMIT requestClipShow(currentItem); 4598 // TODO use undo / redo for bin clip edit effect 4599 // 4600 EditEffectCommand *command = new EditEffectCommand(this, clip->track(), clip->startPos(), effect, newEffect, clip->selectedEffectIndex(), 4601 true, true); 4602 m_commandStack->push(command); 4603 Q_EMIT clipItemSelected(clip); 4604 } 4605 4606 // Q_EMIT gotFilterJobResults(id, startPos, track, results, filterInfo); 4607 return; 4608 } 4609 // This is a timeline filter, forward results 4610 Q_EMIT gotFilterJobResults(id, startPos, track, results, filterInfo); 4611 return; 4612 } 4613 // Currently, only the first value of results is used 4614 std::shared_ptr<ProjectClip> clip = getBinClip(id); 4615 if (!clip) { 4616 return; 4617 } 4618 // Check for return value 4619 int markersType = -1; 4620 if (filterInfo.contains(QStringLiteral("addmarkers"))) { 4621 markersType = filterInfo.value(QStringLiteral("addmarkers")).toInt(); 4622 } 4623 if (results.isEmpty()) { 4624 Q_EMIT displayBinMessage(i18n("No data returned from clip analysis"), KMessageWidget::Warning); 4625 return; 4626 } 4627 bool dataProcessed = false; 4628 QString label = filterInfo.value(QStringLiteral("label")); 4629 QString key = filterInfo.value(QStringLiteral("key")); 4630 int offset = filterInfo.value(QStringLiteral("offset")).toInt(); 4631 QStringList value = results.value(key).split(QLatin1Char(';'), Qt::SkipEmptyParts); 4632 // qCDebug(KDENLIVE_LOG)<<"// RESULT; "<<key<<" = "<<value; 4633 if (filterInfo.contains(QStringLiteral("resultmessage"))) { 4634 QString mess = filterInfo.value(QStringLiteral("resultmessage")); 4635 mess.replace(QLatin1String("%count"), QString::number(value.count())); 4636 Q_EMIT displayBinMessage(mess, KMessageWidget::Information); 4637 } else { 4638 Q_EMIT displayBinMessage(i18n("Processing data analysis"), KMessageWidget::Information); 4639 } 4640 if (filterInfo.contains(QStringLiteral("cutscenes"))) { 4641 // Check if we want to cut scenes from returned data 4642 dataProcessed = true; 4643 int cutPos = 0; 4644 auto *command = new QUndoCommand(); 4645 command->setText(i18n("Auto Split Clip")); 4646 for (const QString &pos : value) { 4647 if (!pos.contains(QLatin1Char('='))) { 4648 continue; 4649 } 4650 int newPos = pos.section(QLatin1Char('='), 0, 0).toInt(); 4651 // Don't use scenes shorter than 1 second 4652 if (newPos - cutPos < 24) { 4653 continue; 4654 } 4655 new AddBinClipCutCommand(this, id, cutPos + offset, newPos + offset, true, command); 4656 cutPos = newPos; 4657 } 4658 if (command->childCount() == 0) { 4659 delete command; 4660 } else { 4661 m_doc->commandStack()->push(command); 4662 } 4663 } 4664 if (markersType >= 0) { 4665 // Add markers from returned data 4666 dataProcessed = true; 4667 int cutPos = 0; 4668 int index = 1; 4669 bool simpleList = false; 4670 double sourceFps = clip->getOriginalFps(); 4671 if (qFuzzyIsNull(sourceFps)) { 4672 sourceFps = pCore->getCurrentFps(); 4673 } 4674 if (filterInfo.contains(QStringLiteral("simplelist"))) { 4675 // simple list 4676 simpleList = true; 4677 } 4678 for (const QString &pos : value) { 4679 if (simpleList) { 4680 clip->getMarkerModel()->addMarker(GenTime((int)(pos.toInt() * pCore->getCurrentFps() / sourceFps), pCore->getCurrentFps()), label + pos, 4681 markersType); 4682 index++; 4683 continue; 4684 } 4685 if (!pos.contains(QLatin1Char('='))) { 4686 continue; 4687 } 4688 int newPos = pos.section(QLatin1Char('='), 0, 0).toInt(); 4689 // Don't use scenes shorter than 1 second 4690 if (newPos - cutPos < 24) { 4691 continue; 4692 } 4693 clip->getMarkerModel()->addMarker(GenTime(newPos + offset, pCore->getCurrentFps()), label + QString::number(index), markersType); 4694 index++; 4695 cutPos = newPos; 4696 } 4697 } 4698 if (!dataProcessed || filterInfo.contains(QStringLiteral("storedata"))) { 4699 // Store returned data as clip extra data 4700 QStringList newValue = clip->updatedAnalysisData(key, results.value(key), offset); 4701 slotAddClipExtraData(id, newValue.at(0), newValue.at(1)); 4702 } 4703 } 4704 */ 4705 4706 // TODO: move title editing into a better place... 4707 void Bin::showTitleWidget(const std::shared_ptr<ProjectClip> &clip) 4708 { 4709 QString path = clip->getProducerProperty(QStringLiteral("resource")); 4710 QDir titleFolder(m_doc->projectDataFolder() + QStringLiteral("/titles")); 4711 titleFolder.mkpath(QStringLiteral(".")); 4712 QList<int> clips = clip->timelineInstances(); 4713 // Temporarily hide this title clip in timeline so that it does not appear when requesting background frame 4714 pCore->temporaryUnplug(clips, true); 4715 TitleWidget dia_ui(QUrl(), titleFolder.absolutePath(), pCore->monitorManager()->projectMonitor(), pCore->window()); 4716 QDomDocument doc; 4717 QString xmldata = clip->getProducerProperty(QStringLiteral("xmldata")); 4718 if (xmldata.isEmpty() && QFile::exists(path)) { 4719 if (!Xml::docContentFromFile(doc, path, false)) { 4720 return; 4721 } 4722 } else { 4723 doc.setContent(xmldata); 4724 } 4725 dia_ui.setXml(doc, clip->clipId()); 4726 int res = dia_ui.exec(); 4727 if (res == QDialog::Accepted) { 4728 pCore->temporaryUnplug(clips, false); 4729 QMap<QString, QString> newprops; 4730 newprops.insert(QStringLiteral("xmldata"), dia_ui.xml().toString()); 4731 if (dia_ui.duration() != clip->duration().frames(pCore->getCurrentFps())) { 4732 // duration changed, we need to update duration 4733 newprops.insert(QStringLiteral("out"), clip->framesToTime(dia_ui.duration() - 1)); 4734 int currentLength = clip->getProducerDuration(); 4735 if (currentLength != dia_ui.duration()) { 4736 newprops.insert(QStringLiteral("kdenlive:duration"), clip->framesToTime(dia_ui.duration())); 4737 } 4738 } 4739 if (clip->clipName().contains(i18n("(copy)"))) { 4740 // We edited a duplicated title clip, update name from new content text 4741 newprops.insert(QStringLiteral("kdenlive:clipname"), dia_ui.titleSuggest()); 4742 if (!path.isEmpty()) { 4743 newprops.insert(QStringLiteral("resource"), QString()); 4744 } 4745 } 4746 if (!path.isEmpty()) { 4747 // we are editing an external file, asked if we want to detach from that file or save the result to that title file. 4748 if (KMessageBox::questionTwoActions(pCore->window(), 4749 i18n("You are editing an external title clip (%1). Do you want to save your changes to the title " 4750 "file or save the changes for this project only?", 4751 path), 4752 i18n("Save Title"), KGuiItem(i18n("Save to title file")), 4753 KGuiItem(i18n("Save in project only"))) == KMessageBox::PrimaryAction) { 4754 // save to external file 4755 dia_ui.saveTitle(QUrl::fromLocalFile(path)); 4756 return; 4757 } else { 4758 newprops.insert(QStringLiteral("resource"), QString()); 4759 } 4760 } 4761 // trigger producer reload 4762 newprops.insert(QStringLiteral("force_reload"), QStringLiteral("1")); 4763 slotEditClipCommand(clip->AbstractProjectItem::clipId(), clip->currentProperties(newprops), newprops); 4764 // when edit is triggered from the timeline, project monitor refresh is necessary after an edit is made 4765 pCore->refreshProjectMonitorOnce(); 4766 } else { 4767 pCore->temporaryUnplug(clips, false); 4768 if (res == QDialog::Accepted + 1) { 4769 // Ready, create clip xml 4770 std::unordered_map<QString, QString> properties; 4771 properties[QStringLiteral("xmldata")] = dia_ui.xml().toString(); 4772 QString titleSuggestion = dia_ui.titleSuggest(); 4773 ClipCreator::createTitleClip(properties, dia_ui.duration(), titleSuggestion.isEmpty() ? i18n("Title clip") : titleSuggestion, 4774 clip->parent()->clipId(), m_itemModel); 4775 } 4776 } 4777 } 4778 4779 void Bin::slotResetInfoMessage() 4780 { 4781 m_errorLog.clear(); 4782 m_currentMessage = BinMessage::BinCategory::NoMessage; 4783 // We cannot delete actions here because of concurrency, it might delete actions meant for the upcoming message 4784 /*QList<QAction *> actions = m_infoMessage->actions(); 4785 for (int i = 0; i < actions.count(); ++i) { 4786 m_infoMessage->removeAction(actions.at(i)); 4787 }*/ 4788 } 4789 4790 void Bin::slotSetSorting() 4791 { 4792 if (m_listType == BinIconView) { 4793 m_proxyModel->setFilterKeyColumn(0); 4794 return; 4795 } 4796 auto *view = qobject_cast<QTreeView *>(m_itemView); 4797 if (view) { 4798 int ix = view->header()->sortIndicatorSection(); 4799 m_proxyModel->setFilterKeyColumn(ix); 4800 } 4801 } 4802 4803 void Bin::slotShowColumn(bool show) 4804 { 4805 auto *act = qobject_cast<QAction *>(sender()); 4806 if (act == nullptr) { 4807 return; 4808 } 4809 auto *view = qobject_cast<QTreeView *>(m_itemView); 4810 if (view) { 4811 view->setColumnHidden(act->data().toInt(), !show); 4812 } 4813 } 4814 4815 void Bin::slotQueryRemoval(const QString &id, const QString &url, const QString &errorMessage) 4816 { 4817 if (m_invalidClipDialog) { 4818 if (!url.isEmpty()) { 4819 m_invalidClipDialog->addClip(id, url); 4820 } 4821 return; 4822 } 4823 QString message = i18n("Clip is invalid, will be removed from project."); 4824 if (!errorMessage.isEmpty()) { 4825 message.append("\n" + errorMessage); 4826 } 4827 m_invalidClipDialog = new InvalidDialog(i18n("Invalid clip"), message, true, this); 4828 m_invalidClipDialog->addClip(id, url); 4829 int result = m_invalidClipDialog->exec(); 4830 if (result == QDialog::Accepted) { 4831 const QStringList ids = m_invalidClipDialog->getIds(); 4832 Fun undo = []() { return true; }; 4833 Fun redo = []() { return true; }; 4834 for (const QString &i : ids) { 4835 auto item = m_itemModel->getClipByBinID(i); 4836 m_itemModel->requestBinClipDeletion(item, undo, redo); 4837 } 4838 } 4839 delete m_invalidClipDialog; 4840 m_invalidClipDialog = nullptr; 4841 } 4842 4843 void Bin::slotRefreshClipThumbnail(const QString &id) 4844 { 4845 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id); 4846 if (!clip) { 4847 return; 4848 } 4849 ClipLoadTask::start(ObjectId(KdenliveObjectType::BinClip, id.toInt(), QUuid()), QDomElement(), true, -1, -1, this); 4850 } 4851 4852 void Bin::slotAddClipExtraData(const QString &id, const QString &key, const QString &clipData) 4853 { 4854 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id); 4855 if (!clip) { 4856 return; 4857 } 4858 QString oldValue = clip->getProducerProperty(key); 4859 QMap<QString, QString> oldProps; 4860 oldProps.insert(key, oldValue); 4861 QMap<QString, QString> newProps; 4862 newProps.insert(key, clipData); 4863 auto *command = new EditClipCommand(this, id, oldProps, newProps, true); 4864 m_doc->commandStack()->push(command); 4865 } 4866 4867 void Bin::slotUpdateClipProperties(const QString &id, const QMap<QString, QString> &properties, bool refreshPropertiesPanel) 4868 { 4869 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getItemByBinId(id); 4870 if (!item) { 4871 // Clip might have been deleted 4872 return; 4873 } 4874 if (item->itemType() == AbstractProjectItem::ClipItem) { 4875 std::shared_ptr<ProjectClip> clip = std::static_pointer_cast<ProjectClip>(item); 4876 if (clip) { 4877 clip->setProperties(properties, refreshPropertiesPanel); 4878 } 4879 } else if (item->itemType() == AbstractProjectItem::SubClipItem) { 4880 std::shared_ptr<ProjectSubClip> clip = std::static_pointer_cast<ProjectSubClip>(item); 4881 if (clip) { 4882 clip->setProperties(properties); 4883 } 4884 } 4885 } 4886 4887 void Bin::showSlideshowWidget(const std::shared_ptr<ProjectClip> &clip) 4888 { 4889 QString folder = QFileInfo(clip->url()).absolutePath(); 4890 qCDebug(KDENLIVE_LOG) << " ** * CLIP ABS PATH: " << clip->url() << " = " << folder; 4891 SlideshowClip *dia = new SlideshowClip(m_doc->timecode(), folder, clip.get(), this); 4892 if (dia->exec() == QDialog::Accepted) { 4893 // edit clip properties 4894 QMap<QString, QString> properties; 4895 properties.insert(QStringLiteral("out"), clip->framesToTime(m_doc->getFramePos(dia->clipDuration()) * dia->imageCount() - 1)); 4896 properties.insert(QStringLiteral("kdenlive:duration"), clip->framesToTime(m_doc->getFramePos(dia->clipDuration()) * dia->imageCount())); 4897 properties.insert(QStringLiteral("kdenlive:clipname"), dia->clipName()); 4898 properties.insert(QStringLiteral("ttl"), QString::number(m_doc->getFramePos(dia->clipDuration()))); 4899 properties.insert(QStringLiteral("loop"), QString::number(static_cast<int>(dia->loop()))); 4900 properties.insert(QStringLiteral("crop"), QString::number(static_cast<int>(dia->crop()))); 4901 properties.insert(QStringLiteral("fade"), QString::number(static_cast<int>(dia->fade()))); 4902 properties.insert(QStringLiteral("luma_duration"), QString::number(m_doc->getFramePos(dia->lumaDuration()))); 4903 properties.insert(QStringLiteral("luma_file"), dia->lumaFile()); 4904 properties.insert(QStringLiteral("softness"), QString::number(dia->softness())); 4905 properties.insert(QStringLiteral("animation"), dia->animation()); 4906 properties.insert(QStringLiteral("low-pass"), QString::number(dia->lowPass())); 4907 4908 QMap<QString, QString> oldProperties; 4909 oldProperties.insert(QStringLiteral("out"), clip->getProducerProperty(QStringLiteral("out"))); 4910 oldProperties.insert(QStringLiteral("kdenlive:duration"), clip->getProducerProperty(QStringLiteral("kdenlive:duration"))); 4911 oldProperties.insert(QStringLiteral("kdenlive:clipname"), clip->name()); 4912 oldProperties.insert(QStringLiteral("ttl"), clip->getProducerProperty(QStringLiteral("ttl"))); 4913 oldProperties.insert(QStringLiteral("loop"), clip->getProducerProperty(QStringLiteral("loop"))); 4914 oldProperties.insert(QStringLiteral("crop"), clip->getProducerProperty(QStringLiteral("crop"))); 4915 oldProperties.insert(QStringLiteral("fade"), clip->getProducerProperty(QStringLiteral("fade"))); 4916 oldProperties.insert(QStringLiteral("luma_duration"), clip->getProducerProperty(QStringLiteral("luma_duration"))); 4917 oldProperties.insert(QStringLiteral("luma_file"), clip->getProducerProperty(QStringLiteral("luma_file"))); 4918 oldProperties.insert(QStringLiteral("softness"), clip->getProducerProperty(QStringLiteral("softness"))); 4919 oldProperties.insert(QStringLiteral("animation"), clip->getProducerProperty(QStringLiteral("animation"))); 4920 oldProperties.insert(QStringLiteral("low-pass"), clip->getProducerProperty(QStringLiteral("low-pass"))); 4921 4922 slotEditClipCommand(clip->AbstractProjectItem::clipId(), oldProperties, properties); 4923 } 4924 delete dia; 4925 } 4926 4927 void Bin::setBinEffectsEnabled(bool enabled, bool refreshMonitor) 4928 { 4929 m_itemModel->setBinEffectsEnabled(enabled); 4930 pCore->projectManager()->disableBinEffects(!enabled, refreshMonitor); 4931 } 4932 4933 void Bin::slotRenameItem() 4934 { 4935 const QModelIndexList indexes = m_proxyModel->selectionModel()->selection().indexes(); 4936 for (const QModelIndex &ix : indexes) { 4937 if (!ix.isValid() || ix.column() != 0) { 4938 continue; 4939 } 4940 m_itemView->setCurrentIndex(ix); 4941 m_itemView->edit(ix); 4942 return; 4943 } 4944 } 4945 4946 void Bin::refreshProxySettings() 4947 { 4948 QList<std::shared_ptr<ProjectClip>> clipList = m_itemModel->getRootFolder()->childClips(); 4949 auto *masterCommand = new QUndoCommand(); 4950 masterCommand->setText(m_doc->useProxy() ? i18n("Enable proxies") : i18n("Disable proxies")); 4951 // en/disable proxy option in clip properties 4952 if (m_propertiesPanel) { 4953 for (QWidget *w : m_propertiesPanel->findChildren<ClipPropertiesController *>()) { 4954 Q_EMIT static_cast<ClipPropertiesController *>(w)->enableProxy(m_doc->useProxy()); 4955 } 4956 } 4957 bool isFolder = false; 4958 const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); 4959 for (const QModelIndex &ix : indexes) { 4960 if (!ix.isValid() || ix.column() != 0) { 4961 continue; 4962 } 4963 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 4964 if (currentItem) { 4965 AbstractProjectItem::PROJECTITEMTYPE itemType = currentItem->itemType(); 4966 isFolder = itemType == AbstractProjectItem::FolderItem; 4967 } 4968 break; 4969 } 4970 m_proxyAction->setEnabled(m_doc->useProxy() && !isFolder); 4971 if (!m_doc->useProxy()) { 4972 // Disable all proxies 4973 m_doc->slotProxyCurrentItem(false, clipList, false, masterCommand); 4974 } else if (m_doc->autoGenerateProxy(-1) || m_doc->autoGenerateImageProxy(-1)) { 4975 QList<std::shared_ptr<ProjectClip>> toProxy; 4976 for (const std::shared_ptr<ProjectClip> &clp : qAsConst(clipList)) { 4977 ClipType::ProducerType t = clp->clipType(); 4978 if (t == ClipType::Playlist && m_doc->autoGenerateProxy(pCore->getCurrentFrameDisplaySize().width())) { 4979 toProxy << clp; 4980 continue; 4981 } else if ((t == ClipType::AV || t == ClipType::Video) && 4982 m_doc->autoGenerateProxy(clp->getProducerIntProperty(QStringLiteral("meta.media.width")))) { 4983 // Start proxy 4984 toProxy << clp; 4985 continue; 4986 } else if (t == ClipType::Image && m_doc->autoGenerateImageProxy(clp->getProducerIntProperty(QStringLiteral("meta.media.width")))) { 4987 // Start proxy 4988 toProxy << clp; 4989 continue; 4990 } 4991 } 4992 if (!toProxy.isEmpty()) { 4993 m_doc->slotProxyCurrentItem(true, toProxy, false, masterCommand); 4994 } 4995 } 4996 if (masterCommand->childCount() > 0) { 4997 m_doc->commandStack()->push(masterCommand); 4998 } else { 4999 delete masterCommand; 5000 } 5001 } 5002 5003 QStringList Bin::getProxyHashList() 5004 { 5005 QStringList list; 5006 QList<std::shared_ptr<ProjectClip>> clipList = m_itemModel->getRootFolder()->childClips(); 5007 for (const std::shared_ptr<ProjectClip> &clp : qAsConst(clipList)) { 5008 if (clp->clipType() == ClipType::AV || clp->clipType() == ClipType::Video || clp->clipType() == ClipType::Playlist) { 5009 list << clp->hash(); 5010 } 5011 } 5012 return list; 5013 } 5014 5015 bool Bin::hasUserClip() const 5016 { 5017 QList<std::shared_ptr<ProjectClip>> allClips = m_itemModel->getRootFolder()->childClips(); 5018 for (auto &c : allClips) { 5019 if (c->clipType() != ClipType::Timeline) { 5020 return true; 5021 } 5022 } 5023 return false; 5024 } 5025 5026 void Bin::reloadAllProducers(bool reloadThumbs) 5027 { 5028 if (m_itemModel->getRootFolder() == nullptr || m_itemModel->getRootFolder()->childCount() == 0 || !isEnabled()) { 5029 return; 5030 } 5031 QList<std::shared_ptr<ProjectClip>> clipList = m_itemModel->getRootFolder()->childClips(); 5032 Q_EMIT openClip(std::shared_ptr<ProjectClip>()); 5033 if (clipList.count() == 1) { 5034 // We only have one clip in the project, so this was called on a reset profile event. 5035 // Check if the clip is included in timeline to update it afterwards 5036 clipList.first()->updateTimelineOnReload(); 5037 } 5038 for (const std::shared_ptr<ProjectClip> &clip : qAsConst(clipList)) { 5039 ClipType::ProducerType type = clip->clipType(); 5040 if (type == ClipType::Timeline) { 5041 continue; 5042 } 5043 QDomDocument doc; 5044 QDomElement xml = clip->toXml(doc, false, false); 5045 // Make sure we reload clip length 5046 if (type == ClipType::AV || type == ClipType::Video || type == ClipType::Audio || type == ClipType::Playlist) { 5047 xml.removeAttribute(QStringLiteral("out")); 5048 Xml::removeXmlProperty(xml, QStringLiteral("length")); 5049 Xml::removeXmlProperty(xml, QStringLiteral("kdenlive:duration")); 5050 } 5051 if (clip->isValid()) { 5052 clip->resetProducerProperty(QStringLiteral("kdenlive:duration")); 5053 if (clip->hasLimitedDuration()) { 5054 clip->resetProducerProperty(QStringLiteral("length")); 5055 } 5056 } 5057 if (!xml.isNull()) { 5058 clip->discardAudioThumb(); 5059 if (reloadThumbs) { 5060 ThumbnailCache::get()->invalidateThumbsForClip(clip->clipId()); 5061 } 5062 clip->setClipStatus(FileStatus::StatusWaiting); 5063 ObjectId oid(KdenliveObjectType::BinClip, clip->clipId().toInt(), QUuid()); 5064 pCore->taskManager.discardJobs(oid, AbstractTask::NOJOBTYPE, true, 5065 {AbstractTask::TRANSCODEJOB, AbstractTask::PROXYJOB, AbstractTask::AUDIOTHUMBJOB}); 5066 ClipLoadTask::start(oid, xml, false, -1, -1, this); 5067 } 5068 } 5069 } 5070 5071 void Bin::checkAudioThumbs() 5072 { 5073 if (!KdenliveSettings::audiothumbnails() || m_itemModel->getRootFolder() == nullptr || m_itemModel->getRootFolder()->childCount() == 0 || !isEnabled()) { 5074 return; 5075 } 5076 QList<std::shared_ptr<ProjectClip>> clipList = m_itemModel->getRootFolder()->childClips(); 5077 for (const auto &clip : qAsConst(clipList)) { 5078 ClipType::ProducerType type = clip->clipType(); 5079 if (type == ClipType::AV || type == ClipType::Audio || type == ClipType::Playlist || type == ClipType::Unknown) { 5080 AudioLevelsTask::start(ObjectId(KdenliveObjectType::BinClip, clip->clipId().toInt(), QUuid()), clip.get(), false); 5081 } 5082 } 5083 } 5084 5085 void Bin::slotMessageActionTriggered() 5086 { 5087 m_infoMessage->animatedHide(); 5088 } 5089 5090 void Bin::getBinStats(uint *used, uint *unused, qint64 *usedSize, qint64 *unusedSize) 5091 { 5092 QList<std::shared_ptr<ProjectClip>> clipList = m_itemModel->getRootFolder()->childClips(); 5093 for (const std::shared_ptr<ProjectClip> &clip : qAsConst(clipList)) { 5094 // Don't count sequence clips here 5095 if (clip->clipType() == ClipType::Timeline) { 5096 continue; 5097 } 5098 if (clip->refCount() == 0) { 5099 *unused += 1; 5100 *unusedSize += clip->getProducerInt64Property(QStringLiteral("kdenlive:file_size")); 5101 } else { 5102 *used += 1; 5103 *usedSize += clip->getProducerInt64Property(QStringLiteral("kdenlive:file_size")); 5104 } 5105 } 5106 } 5107 5108 void Bin::rebuildProxies() 5109 { 5110 QList<std::shared_ptr<ProjectClip>> clipList = m_itemModel->getRootFolder()->childClips(); 5111 QList<std::shared_ptr<ProjectClip>> toProxy; 5112 for (const std::shared_ptr<ProjectClip> &clp : qAsConst(clipList)) { 5113 if (clp->hasProxy()) { 5114 toProxy << clp; 5115 // Abort all pending jobs 5116 pCore->taskManager.discardJobs(ObjectId(KdenliveObjectType::BinClip, clp->clipId().toInt(), QUuid()), AbstractTask::PROXYJOB); 5117 clp->deleteProxy(false); 5118 } 5119 } 5120 if (toProxy.isEmpty()) { 5121 return; 5122 } 5123 auto *masterCommand = new QUndoCommand(); 5124 masterCommand->setText(i18n("Rebuild proxies")); 5125 m_doc->slotProxyCurrentItem(true, toProxy, true, masterCommand); 5126 if (masterCommand->childCount() > 0) { 5127 m_doc->commandStack()->push(masterCommand); 5128 } else { 5129 delete masterCommand; 5130 } 5131 } 5132 5133 void Bin::showClearButton(bool show) 5134 { 5135 m_searchLine->setClearButtonEnabled(show); 5136 } 5137 5138 void Bin::saveZone(const QStringList &info, const QDir &dir) 5139 { 5140 if (info.size() != 3) { 5141 return; 5142 } 5143 std::shared_ptr<ProjectClip> clip = getBinClip(info.constFirst()); 5144 if (clip) { 5145 QPoint zone(info.at(1).toInt(), info.at(2).toInt()); 5146 clip->saveZone(zone, dir); 5147 } 5148 } 5149 5150 void Bin::setCurrent(const std::shared_ptr<AbstractProjectItem> &item) 5151 { 5152 switch (item->itemType()) { 5153 case AbstractProjectItem::ClipItem: { 5154 std::shared_ptr<ProjectClip> clp = std::static_pointer_cast<ProjectClip>(item); 5155 if (clp && clp->statusReady()) { 5156 openProducer(clp); 5157 Q_EMIT requestShowEffectStack(clp->clipName(), clp->m_effectStack, clp->getFrameSize(), false); 5158 } 5159 break; 5160 } 5161 case AbstractProjectItem::SubClipItem: { 5162 auto subClip = std::static_pointer_cast<ProjectSubClip>(item); 5163 QPoint zone = subClip->zone(); 5164 std::shared_ptr<ProjectClip> master = subClip->getMasterClip(); 5165 if (master && master->statusReady()) { 5166 openProducer(master, zone.x(), zone.y() + 1); 5167 } 5168 break; 5169 } 5170 case AbstractProjectItem::FolderItem: 5171 openProducer(nullptr); 5172 } 5173 } 5174 5175 void Bin::cleanupUnused() 5176 { 5177 m_itemModel->requestCleanupUnused(); 5178 } 5179 5180 size_t Bin::getClipDuration(int itemId) const 5181 { 5182 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(QString::number(itemId)); 5183 Q_ASSERT(clip != nullptr); 5184 return clip->frameDuration(); 5185 } 5186 5187 QSize Bin::getFrameSize(int itemId) const 5188 { 5189 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(QString::number(itemId)); 5190 Q_ASSERT(clip != nullptr); 5191 return clip->frameSize(); 5192 } 5193 5194 PlaylistState::ClipState Bin::getClipState(int itemId) const 5195 { 5196 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(QString::number(itemId)); 5197 Q_ASSERT(clip != nullptr); 5198 bool audio = clip->hasAudio(); 5199 bool video = clip->hasVideo(); 5200 return audio ? (video ? PlaylistState::Disabled : PlaylistState::AudioOnly) : PlaylistState::VideoOnly; 5201 } 5202 5203 QString Bin::getCurrentFolder() 5204 { 5205 // Check parent item 5206 QModelIndex ix = m_proxyModel->selectionModel()->currentIndex(); 5207 std::shared_ptr<ProjectFolder> parentFolder = m_itemModel->getRootFolder(); 5208 if (ix.isValid() && m_proxyModel->selectionModel()->isSelected(ix)) { 5209 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 5210 parentFolder = std::static_pointer_cast<ProjectFolder>(currentItem->getEnclosingFolder()); 5211 } else { 5212 QModelIndex parentIx = m_itemView->rootIndex(); 5213 if (parentIx.isValid()) { 5214 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(parentIx)); 5215 if (item && item != parentFolder) { 5216 parentFolder = std::static_pointer_cast<ProjectFolder>(item->getEnclosingFolder()); 5217 } 5218 } 5219 } 5220 return parentFolder->clipId(); 5221 } 5222 5223 void Bin::adjustProjectProfileToItem() 5224 { 5225 const QString clipId = m_monitor->activeClipId(); 5226 slotCheckProfile(clipId); 5227 } 5228 5229 void Bin::slotCheckProfile(const QString &binId) 5230 { 5231 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(binId); 5232 if (clip && clip->statusReady()) { 5233 checkProfile(clip->originalProducer()); 5234 } 5235 } 5236 5237 // static 5238 void Bin::checkProfile(const std::shared_ptr<Mlt::Producer> &producer) 5239 { 5240 // Check if clip profile matches 5241 QString service = producer->get("mlt_service"); 5242 // Check for image producer 5243 if (service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) { 5244 // This is an image, create profile from image size 5245 int width = producer->get_int("meta.media.width"); 5246 if (width % 2 > 0) { 5247 width += width % 2; 5248 } 5249 int height = producer->get_int("meta.media.height"); 5250 height += height % 2; 5251 if (width > 100 && height > 100 && pCore->getCurrentFrameSize() != QSize(width, height)) { 5252 std::unique_ptr<ProfileParam> projectProfile(new ProfileParam(pCore->getCurrentProfile().get())); 5253 projectProfile->m_width = width; 5254 projectProfile->m_height = height; 5255 projectProfile->m_sample_aspect_num = 1; 5256 projectProfile->m_sample_aspect_den = 1; 5257 projectProfile->m_display_aspect_num = width; 5258 projectProfile->m_display_aspect_den = height; 5259 projectProfile->m_description.clear(); 5260 QMetaObject::invokeMethod(pCore->currentDoc(), "switchProfile", Q_ARG(ProfileParam *, new ProfileParam(projectProfile.get())), 5261 Q_ARG(QString, QFileInfo(producer->get("resource")).fileName())); 5262 } else { 5263 // Very small image, we probably don't want to use this as profile 5264 } 5265 } else if (service.contains(QStringLiteral("avformat"))) { 5266 std::unique_ptr<Mlt::Profile> blankProfile(new Mlt::Profile()); 5267 blankProfile->set_explicit(0); 5268 blankProfile->from_producer(*producer); 5269 std::unique_ptr<ProfileParam> clipProfile(new ProfileParam(blankProfile.get())); 5270 std::unique_ptr<ProfileParam> projectProfile(new ProfileParam(pCore->getCurrentProfile().get())); 5271 clipProfile->adjustDimensions(); 5272 if (*clipProfile.get() == *projectProfile.get()) { 5273 if (KdenliveSettings::default_profile().isEmpty()) { 5274 // Confirm default project format 5275 KdenliveSettings::setDefault_profile(pCore->getCurrentProfile()->path()); 5276 } 5277 } else { 5278 // Profiles do not match, propose profile adjustment 5279 QMetaObject::invokeMethod(pCore->currentDoc(), "switchProfile", Q_ARG(ProfileParam *, new ProfileParam(clipProfile.get())), 5280 Q_ARG(QString, QFileInfo(producer->get("resource")).fileName())); 5281 } 5282 } 5283 } 5284 5285 void Bin::showBinFrame(const QModelIndex &ix, int frame, bool storeFrame) 5286 { 5287 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 5288 if (item) { 5289 ClipType::ProducerType type = item->clipType(); 5290 if (type != ClipType::AV && type != ClipType::Video && type != ClipType::Playlist && type != ClipType::SlideShow) { 5291 return; 5292 } 5293 if (item->itemType() == AbstractProjectItem::ClipItem) { 5294 auto clip = std::static_pointer_cast<ProjectClip>(item); 5295 if (clip && (clip->clipType() == ClipType::AV || clip->clipType() == ClipType::Video || clip->clipType() == ClipType::Playlist)) { 5296 clip->getThumbFromPercent(frame, storeFrame); 5297 } 5298 } else if (item->itemType() == AbstractProjectItem::SubClipItem) { 5299 auto clip = std::static_pointer_cast<ProjectSubClip>(item); 5300 if (clip && (clip->clipType() == ClipType::AV || clip->clipType() == ClipType::Video || clip->clipType() == ClipType::Playlist)) { 5301 clip->getThumbFromPercent(frame); 5302 } 5303 } 5304 } 5305 } 5306 5307 void Bin::invalidateClip(const QString &binId) 5308 { 5309 std::shared_ptr<ProjectClip> clip = getBinClip(binId); 5310 if (clip && clip->clipType() != ClipType::Audio) { 5311 QMap<QUuid, QList<int>> allIds = clip->getAllTimelineInstances(); 5312 QMapIterator<QUuid, QList<int>> i(allIds); 5313 while (i.hasNext()) { 5314 i.next(); 5315 QList<int> values = i.value(); 5316 for (int j : qAsConst(values)) { 5317 pCore->invalidateItem(ObjectId(KdenliveObjectType::TimelineClip, j, i.key())); 5318 } 5319 } 5320 } 5321 } 5322 5323 QSize Bin::sizeHint() const 5324 { 5325 return QSize(350, pCore->window()->height() / 2); 5326 } 5327 5328 void Bin::slotBack() 5329 { 5330 QModelIndex currentRootIx = m_itemView->rootIndex(); 5331 if (!currentRootIx.isValid()) { 5332 return; 5333 } 5334 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(currentRootIx)); 5335 if (!item) { 5336 qDebug() << "=== ERROR CANNOT FIND ROOT FOR CURRENT VIEW"; 5337 return; 5338 } 5339 std::shared_ptr<AbstractProjectItem> parentItem = item->parent(); 5340 if (!parentItem) { 5341 qDebug() << "=== ERROR CANNOT FIND PARENT FOR CURRENT VIEW"; 5342 return; 5343 } 5344 if (parentItem != m_itemModel->getRootFolder()) { 5345 // We are entering a parent folder 5346 QModelIndex parentId = getIndexForId(parentItem->clipId(), parentItem->itemType() == AbstractProjectItem::FolderItem); 5347 if (parentId.isValid()) { 5348 m_itemView->setRootIndex(m_proxyModel->mapFromSource(parentId)); 5349 parentWidget()->setWindowTitle(parentItem->name()); 5350 } 5351 } else { 5352 m_itemView->setRootIndex(QModelIndex()); 5353 m_upAction->setEnabled(false); 5354 parentWidget()->setWindowTitle(i18nc("@title:window", "Project Bin")); 5355 } 5356 } 5357 5358 void Bin::checkProjectAudioTracks(QString clipId, int minimumTracksCount) 5359 { 5360 if (m_currentMessage == BinMessage::BinCategory::ProfileMessage) { 5361 // Don't show this message if another one is active 5362 return; 5363 } 5364 int requestedTracks = minimumTracksCount - pCore->projectManager()->avTracksCount().first; 5365 if (requestedTracks > 0) { 5366 if (clipId.isEmpty()) { 5367 clipId = m_monitor->activeClipId(); 5368 } 5369 QList<QAction *> list; 5370 QAction *ac = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("Add Tracks"), this); 5371 connect(ac, &QAction::triggered, [requestedTracks]() { pCore->projectManager()->addAudioTracks(requestedTracks); }); 5372 QAction *ac2 = new QAction(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Edit Streams"), this); 5373 connect(ac2, &QAction::triggered, this, [this, clipId]() { 5374 selectClipById(clipId); 5375 if (m_propertiesPanel) { 5376 for (QWidget *w : m_propertiesPanel->findChildren<ClipPropertiesController *>()) { 5377 if (w->parentWidget() && w->parentWidget()->parentWidget()) { 5378 // Raise panel 5379 w->parentWidget()->parentWidget()->show(); 5380 w->parentWidget()->parentWidget()->raise(); 5381 } 5382 // Show audio tab 5383 static_cast<ClipPropertiesController *>(w)->activatePage(2); 5384 } 5385 } 5386 }); 5387 QAction *ac3 = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("Don't ask again"), this); 5388 connect(ac3, &QAction::triggered, [&]() { KdenliveSettings::setMultistream_checktrack(false); }); 5389 // QAction *ac4 = new QAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("Cancel"), this); 5390 list << ac << ac2 << ac3; // << ac4; 5391 doDisplayMessage(i18n("Your project needs more audio tracks to handle all streams. Add %1 audio tracks ?", requestedTracks), 5392 KMessageWidget::Information, list, true, BinMessage::BinCategory::StreamsMessage); 5393 } else if (m_currentMessage == BinMessage::BinCategory::StreamsMessage) { 5394 // Clip streams number ok for the project, hide message 5395 m_infoMessage->animatedHide(); 5396 } 5397 } 5398 5399 void Bin::addClipMarker(const QString &binId, const QList<int> &positions, const QStringList &comments) 5400 { 5401 std::shared_ptr<ProjectClip> clip = getBinClip(binId); 5402 if (!clip) { 5403 pCore->displayMessage(i18n("Cannot find clip to add marker"), ErrorMessage); 5404 return; 5405 } 5406 QMap<GenTime, QString> markers; 5407 int ix = 0; 5408 for (int pos : positions) { 5409 GenTime p(pos, pCore->getCurrentFps()); 5410 if (comments.size() == positions.size()) { 5411 markers.insert(p, comments.at(ix)); 5412 } else { 5413 markers.insert(p, pCore->currentDoc()->timecode().getDisplayTimecode(p, false)); 5414 } 5415 ix++; 5416 } 5417 clip->getMarkerModel()->addMarkers(markers, KdenliveSettings::default_marker_type()); 5418 } 5419 5420 void Bin::checkMissingProxies() 5421 { 5422 if (m_itemModel->getRootFolder() == nullptr || m_itemModel->getRootFolder()->childCount() == 0) { 5423 return; 5424 } 5425 QList<std::shared_ptr<ProjectClip>> clipList = m_itemModel->getRootFolder()->childClips(); 5426 QList<std::shared_ptr<ProjectClip>> toProxy; 5427 for (const auto &clip : qAsConst(clipList)) { 5428 if (clip->getProducerIntProperty(QStringLiteral("_replaceproxy")) > 0) { 5429 clip->resetProducerProperty(QStringLiteral("_replaceproxy")); 5430 toProxy << clip; 5431 } 5432 } 5433 if (!toProxy.isEmpty()) { 5434 pCore->currentDoc()->slotProxyCurrentItem(true, toProxy); 5435 } 5436 } 5437 5438 void Bin::saveFolderState() 5439 { 5440 // Check folder state (expanded or not) 5441 if (m_itemView == nullptr || m_listType != BinTreeView) { 5442 // Folder state is only valid in tree view mode 5443 return; 5444 } 5445 auto *view = static_cast<QTreeView *>(m_itemView); 5446 QList<std::shared_ptr<ProjectFolder>> folders = m_itemModel->getFolders(); 5447 QStringList expandedFolders; 5448 for (const auto &folder : qAsConst(folders)) { 5449 QModelIndex ix = m_itemModel->getIndexFromItem(folder); 5450 if (view->isExpanded(m_proxyModel->mapFromSource(ix))) { 5451 // Save expanded state 5452 expandedFolders << folder->clipId(); 5453 } 5454 } 5455 m_itemModel->saveProperty(QStringLiteral("kdenlive:expandedFolders"), expandedFolders.join(QLatin1Char(';'))); 5456 m_itemModel->saveProperty(QStringLiteral("kdenlive:binZoom"), QString::number(KdenliveSettings::bin_zoom())); 5457 } 5458 5459 void Bin::loadBinProperties(const QStringList &foldersToExpand, int zoomLevel) 5460 { 5461 // Check folder state (expanded or not) 5462 if (m_itemView == nullptr || m_listType != BinTreeView) { 5463 // Folder state is only valid in tree view mode 5464 return; 5465 } 5466 auto *view = static_cast<QTreeView *>(m_itemView); 5467 for (const QString &id : foldersToExpand) { 5468 std::shared_ptr<ProjectFolder> folder = m_itemModel->getFolderByBinId(id); 5469 if (folder) { 5470 QModelIndex ix = m_itemModel->getIndexFromItem(folder); 5471 view->setExpanded(m_proxyModel->mapFromSource(ix), true); 5472 } 5473 } 5474 if (zoomLevel > -1) { 5475 m_slider->setValue(zoomLevel); 5476 } 5477 } 5478 5479 QList<int> Bin::getUsedClipIds() 5480 { 5481 QList<int> timelineClipIds; 5482 QList<std::shared_ptr<ProjectClip>> allClipIds = m_itemModel->getRootFolder()->childClips(); 5483 for (const auto &clip : qAsConst(allClipIds)) { 5484 if (clip->isIncludedInTimeline()) { 5485 timelineClipIds.push_back(clip->binId().toInt()); 5486 } 5487 } 5488 return timelineClipIds; 5489 } 5490 5491 void Bin::savePlaylist(const QString &binId, const QString &savePath, const QVector<QPoint> &zones, const QMap<QString, QString> &properties, bool createNew) 5492 { 5493 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(binId); 5494 if (!clip) { 5495 pCore->displayMessage(i18n("Could not find master clip"), MessageType::ErrorMessage, 300); 5496 return; 5497 } 5498 Mlt::Tractor t(pCore->getProjectProfile()); 5499 std::shared_ptr<Mlt::Producer> prod(new Mlt::Producer(clip->originalProducer().get())); 5500 Mlt::Playlist main(pCore->getProjectProfile()); 5501 main.set("id", "main_bin"); 5502 main.set("xml_retain", 1); 5503 // Here we could store some kdenlive settings in the main playlist 5504 /*QMapIterator<QString, QString> i(properties); 5505 while (i.hasNext()) { 5506 i.next(); 5507 main.set(i.key().toUtf8().constData(), i.value().toUtf8().constData()); 5508 }*/ 5509 main.append(*prod.get()); 5510 t.set("xml_retain main_bin", main.get_service(), 0); 5511 Mlt::Playlist pl(pCore->getProjectProfile()); 5512 for (auto &zone : zones) { 5513 std::shared_ptr<Mlt::Producer> cut(prod->cut(zone.x(), zone.y())); 5514 pl.append(*cut.get()); 5515 } 5516 t.set_track(pl, 0); 5517 Mlt::Consumer cons(pCore->getProjectProfile(), "xml", savePath.toUtf8().constData()); 5518 cons.set("store", "kdenlive"); 5519 cons.connect(t); 5520 cons.run(); 5521 if (createNew) { 5522 const QString id = slotAddClipToProject(QUrl::fromLocalFile(savePath)); 5523 // Set properties directly on the clip 5524 std::shared_ptr<ProjectClip> playlistClip = m_itemModel->getClipByBinID(id); 5525 QMapIterator<QString, QString> i(properties); 5526 while (i.hasNext()) { 5527 i.next(); 5528 playlistClip->setProducerProperty(i.key(), i.value()); 5529 } 5530 selectClipById(id); 5531 } 5532 } 5533 5534 void Bin::requestSelectionTranscoding(bool forceReplace) 5535 { 5536 if (m_transcodingDialog == nullptr) { 5537 m_transcodingDialog = new TranscodeSeek(false, true, this); 5538 connect(m_transcodingDialog, &QDialog::accepted, this, [&]() { 5539 bool replace = m_transcodingDialog->replace_original->isChecked(); 5540 if (!forceReplace) { 5541 KdenliveSettings::setTranscodingReplace(replace); 5542 } 5543 QMap<QString, QStringList> ids = m_transcodingDialog->ids(); 5544 QMapIterator<QString, QStringList> i(ids); 5545 while (i.hasNext()) { 5546 i.next(); 5547 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(i.key()); 5548 ObjectId oid(KdenliveObjectType::BinClip, i.key().toInt(), QUuid()); 5549 if (clip->clipType() == ClipType::Timeline) { 5550 // Ensure we use the correct out point 5551 TranscodeTask::start(oid, i.value().first(), m_transcodingDialog->preParams(), m_transcodingDialog->params(i.value().at(1).toInt()), 0, 5552 clip->frameDuration(), replace, clip.get(), false, false); 5553 } else { 5554 TranscodeTask::start(oid, i.value().first(), m_transcodingDialog->preParams(), m_transcodingDialog->params(i.value().at(1).toInt()), -1, -1, 5555 replace, clip.get(), false, false); 5556 } 5557 } 5558 m_transcodingDialog->deleteLater(); 5559 m_transcodingDialog = nullptr; 5560 }); 5561 connect(m_transcodingDialog, &QDialog::rejected, this, [&]() { 5562 m_transcodingDialog->deleteLater(); 5563 m_transcodingDialog = nullptr; 5564 }); 5565 } 5566 std::vector<QString> ids = selectedClipsIds(); 5567 for (const auto &id : ids) { 5568 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id); 5569 if (clip) { 5570 const QString clipService = clip->getProducerProperty(QStringLiteral("mlt_service")); 5571 if (clipService.startsWith(QLatin1String("avformat"))) { 5572 QString resource = clip->clipUrl(); 5573 ClipType::ProducerType type = clip->clipType(); 5574 int integerFps = qRound(clip->originalFps()); 5575 QString suffix = QString("-%1fps").arg(integerFps); 5576 m_transcodingDialog->addUrl(resource, id, suffix, type, QString()); 5577 } else { 5578 m_transcodingDialog->addUrl(clip->clipName(), id, QString(), clip->clipType(), QString()); 5579 } 5580 } 5581 } 5582 m_transcodingDialog->show(); 5583 } 5584 5585 void Bin::requestTranscoding(const QString &url, const QString &id, int type, bool checkProfile, const QString &suffix, const QString &message) 5586 { 5587 if (m_transcodingDialog == nullptr) { 5588 m_transcodingDialog = new TranscodeSeek(false, false, this); 5589 m_transcodingDialog->replace_original->setVisible(false); 5590 connect(m_transcodingDialog, &QDialog::accepted, this, [&, checkProfile]() { 5591 QMap<QString, QStringList> ids = m_transcodingDialog->ids(); 5592 if (!ids.isEmpty()) { 5593 QString firstId = ids.firstKey(); 5594 QMapIterator<QString, QStringList> i(ids); 5595 while (i.hasNext()) { 5596 i.next(); 5597 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(i.key()); 5598 TranscodeTask::start(ObjectId(KdenliveObjectType::BinClip, i.key().toInt(), QUuid()), i.value().first(), m_transcodingDialog->preParams(), 5599 m_transcodingDialog->params(i.value().at(1).toInt()), -1, -1, true, clip.get(), false, 5600 i.key() == firstId ? checkProfile : false); 5601 } 5602 } 5603 m_transcodingDialog->deleteLater(); 5604 m_transcodingDialog = nullptr; 5605 }); 5606 connect(m_transcodingDialog, &QDialog::rejected, this, [&, checkProfile]() { 5607 QMap<QString, QStringList> ids = m_transcodingDialog->ids(); 5608 QString firstId; 5609 if (!ids.isEmpty()) { 5610 firstId = ids.firstKey(); 5611 } 5612 m_transcodingDialog->deleteLater(); 5613 m_transcodingDialog = nullptr; 5614 if (checkProfile && !firstId.isEmpty()) { 5615 pCore->bin()->slotCheckProfile(firstId); 5616 } 5617 }); 5618 } 5619 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id); 5620 if (clip) { 5621 ClipType::ProducerType cType = (ClipType::ProducerType)type; 5622 if (cType == ClipType::Unknown) { 5623 cType = clip->clipType(); 5624 } 5625 if (url.isEmpty()) { 5626 QString resource = clip->clipUrl(); 5627 m_transcodingDialog->addUrl(resource, id, suffix, cType, message); 5628 } else { 5629 m_transcodingDialog->addUrl(url, id, suffix, cType, message); 5630 } 5631 } 5632 m_transcodingDialog->show(); 5633 } 5634 5635 void Bin::addFilterToClip(const QString &sourceClipId, const QString &filterId, stringMap params) 5636 { 5637 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(sourceClipId); 5638 if (clip) { 5639 clip->addEffect(filterId, params); 5640 } 5641 } 5642 5643 bool Bin::addProjectClipInFolder(const QString &path, const QString &sourceClipId, const QString &sourceFolder, const QString &jobId) 5644 { 5645 // Check if the clip is already inserted in the project, if yes exit 5646 QStringList existingIds = m_itemModel->getClipByUrl(QFileInfo(path)); 5647 if (!existingIds.isEmpty()) { 5648 return true; 5649 } 5650 Fun undo = []() { return true; }; 5651 Fun redo = []() { return true; }; 5652 std::pair<ClipJobManager::JobCompletionAction, QString> jobAction = ClipJobManager::getJobAction(jobId); 5653 if (jobAction.first == ClipJobManager::JobCompletionAction::ReplaceOriginal) { 5654 // Simply replace source clip with stabilized version 5655 replaceSingleClip(sourceClipId, path); 5656 return true; 5657 } 5658 // Check if folder exists 5659 QString folderId = QStringLiteral("-1"); 5660 std::shared_ptr<ProjectFolder> baseFolder = nullptr; 5661 if (jobAction.first == ClipJobManager::JobCompletionAction::SubFolder) { 5662 baseFolder = m_itemModel->getFolderByBinId(sourceFolder); 5663 } 5664 if (baseFolder == nullptr) { 5665 baseFolder = m_itemModel->getRootFolder(); 5666 } 5667 if (jobAction.second.isEmpty()) { 5668 // Put clip in sourceFolder if we don't have a folder name 5669 folderId = sourceFolder; 5670 } else { 5671 bool found = false; 5672 for (int i = 0; i < baseFolder->childCount(); ++i) { 5673 auto currentItem = std::static_pointer_cast<AbstractProjectItem>(baseFolder->child(i)); 5674 if (currentItem->itemType() == AbstractProjectItem::FolderItem && currentItem->name() == jobAction.second) { 5675 found = true; 5676 folderId = currentItem->clipId(); 5677 break; 5678 } 5679 } 5680 5681 if (!found) { 5682 // if it was not found, create folder 5683 m_itemModel->requestAddFolder(folderId, jobAction.second, baseFolder->clipId(), undo, redo); 5684 } 5685 } 5686 std::function<void(const QString &)> callBack = [this, sourceClipId, jobAction, path](const QString &binId) { 5687 if (!binId.isEmpty()) { 5688 if (jobAction.first != ClipJobManager::JobCompletionAction::ReplaceOriginal) { 5689 // Clip was added to Bin, select it if replaced clip is still selected 5690 QModelIndex ix = m_proxyModel->selectionModel()->currentIndex(); 5691 if (ix.isValid()) { 5692 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 5693 if (currentItem->clipId() == sourceClipId) { 5694 selectClipById(binId); 5695 } 5696 } 5697 } 5698 } 5699 }; 5700 auto id = ClipCreator::createClipFromFile(path, folderId, m_itemModel, undo, redo, callBack); 5701 bool ok = (id != QStringLiteral("-1")); 5702 if (ok) { 5703 pCore->pushUndo(undo, redo, i18nc("@action", "Add clip")); 5704 } 5705 return ok; 5706 } 5707 5708 void Bin::updateClipsCount() 5709 { 5710 int count = m_itemModel->clipsCount(); 5711 if (count < 2) { 5712 m_clipsCountMessage = QString(); 5713 } else { 5714 int selected = 0; 5715 const QModelIndexList indexes = m_proxyModel->selectionModel()->selection().indexes(); 5716 for (const QModelIndex &ix : indexes) { 5717 if (ix.isValid() && ix.column() == 0) { 5718 std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); 5719 if (item->itemType() == AbstractProjectItem::ClipItem) { 5720 selected++; 5721 } 5722 } 5723 } 5724 if (selected == 0) { 5725 m_clipsCountMessage = i18n("<b>%1</b> clips | ", count); 5726 } else { 5727 m_clipsCountMessage = i18n("<b>%1</b> clips (%2 selected) | ", count, selected); 5728 } 5729 } 5730 showBinInfo(); 5731 } 5732 5733 void Bin::updateKeyBinding(const QString &bindingMessage) 5734 { 5735 m_keyBindingMessage = bindingMessage; 5736 showBinInfo(); 5737 } 5738 5739 void Bin::showBinInfo() 5740 { 5741 pCore->window()->showKeyBinding(QString("%1%2").arg(m_clipsCountMessage, m_keyBindingMessage)); 5742 } 5743 5744 bool Bin::containsId(const QString &clipId) const 5745 { 5746 if (m_listType == BinIconView) { 5747 // Check if the clip is in our folder 5748 const QString rootId = m_itemView->rootIndex().data(AbstractProjectItem::DataId).toString(); 5749 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(clipId); 5750 if (clip) { 5751 std::shared_ptr<AbstractProjectItem> par = clip->parent(); 5752 if (par && par->clipId() == rootId) { 5753 return true; 5754 } 5755 } 5756 return false; 5757 } else { 5758 } 5759 return true; 5760 } 5761 5762 void Bin::processMultiStream(const QString &clipId, QList<int> videoStreams, QList<int> audioStreams) 5763 { 5764 auto binClip = m_itemModel->getClipByBinID(clipId); 5765 // We retrieve the folder containing our clip, because we will set the other streams in the same 5766 std::shared_ptr<AbstractProjectItem> baseFolder = binClip->parent(); 5767 if (!baseFolder) { 5768 baseFolder = m_itemModel->getRootFolder(); 5769 } 5770 const QString parentId = baseFolder->clipId(); 5771 std::shared_ptr<Mlt::Producer> producer = binClip->originalProducer(); 5772 // This helper lambda request addition of a given stream 5773 auto addStream = [this, parentId, producer](int vindex, int vstream, int aindex, int astream, Fun &undo, Fun &redo) { 5774 auto clone = ProjectClip::cloneProducer(producer); 5775 clone->set("video_index", vindex); 5776 clone->set("audio_index", aindex); 5777 clone->set("vstream", vstream); 5778 clone->set("astream", astream); 5779 QString id; 5780 m_itemModel->requestAddBinClip(id, clone, parentId, undo, redo); 5781 }; 5782 Fun undo = []() { return true; }; 5783 Fun redo = []() { return true; }; 5784 5785 if (KdenliveSettings::automultistreams()) { 5786 for (int i = 1; i < videoStreams.count(); ++i) { 5787 int vindex = videoStreams.at(i); 5788 int aindex = 0; 5789 if (i <= audioStreams.count() - 1) { 5790 aindex = audioStreams.at(i); 5791 } 5792 addStream(vindex, i - 1, aindex, qMin(i - 1, audioStreams.count() - 1), undo, redo); 5793 } 5794 pCore->pushUndo(undo, redo, i18np("Add additional stream for clip", "Add additional streams for clip", videoStreams.count() - 1)); 5795 return; 5796 } 5797 5798 int width = int(60.0 * pCore->getCurrentDar()); 5799 if (width % 2 == 1) { 5800 width++; 5801 } 5802 5803 QScopedPointer<QDialog> dialog(new QDialog(qApp->activeWindow())); 5804 dialog->setWindowTitle(QStringLiteral("Multi Stream Clip")); 5805 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 5806 QWidget *mainWidget = new QWidget(dialog.data()); 5807 auto *mainLayout = new QVBoxLayout; 5808 dialog->setLayout(mainLayout); 5809 mainLayout->addWidget(mainWidget); 5810 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); 5811 okButton->setDefault(true); 5812 okButton->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Return)); 5813 dialog->connect(buttonBox, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept); 5814 dialog->connect(buttonBox, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject); 5815 okButton->setText(i18n("Import selected clips")); 5816 5817 QLabel *lab1 = new QLabel(i18n("Additional streams for clip\n %1", binClip->clipName()), mainWidget); 5818 mainLayout->addWidget(lab1); 5819 QList<QGroupBox *> groupList; 5820 QList<QComboBox *> comboList; 5821 // We start loading the list at 1, video index 0 should already be loaded 5822 for (int j = 1; j < videoStreams.count(); ++j) { 5823 auto clone = ProjectClip::cloneProducer(producer); 5824 clone->set("video_index", videoStreams.at(j)); 5825 if (clone == nullptr || !clone->is_valid()) { 5826 continue; 5827 } 5828 // TODO this keyframe should be cached 5829 QImage thumb = KThumb::getFrame(clone.get(), 0, width, 60); 5830 QGroupBox *streamFrame = new QGroupBox(i18n("Video stream %1", videoStreams.at(j)), mainWidget); 5831 mainLayout->addWidget(streamFrame); 5832 streamFrame->setProperty("vindex", videoStreams.at(j)); 5833 groupList << streamFrame; 5834 streamFrame->setCheckable(true); 5835 streamFrame->setChecked(true); 5836 auto *vh = new QVBoxLayout(streamFrame); 5837 QLabel *iconLabel = new QLabel(mainWidget); 5838 mainLayout->addWidget(iconLabel); 5839 iconLabel->setPixmap(QPixmap::fromImage(thumb)); 5840 vh->addWidget(iconLabel); 5841 if (audioStreams.count() > 1) { 5842 auto *cb = new QComboBox(mainWidget); 5843 mainLayout->addWidget(cb); 5844 for (int k = 0; k < audioStreams.count(); ++k) { 5845 cb->addItem(i18n("Audio stream %1", audioStreams.at(k)), audioStreams.at(k)); 5846 } 5847 comboList << cb; 5848 cb->setCurrentIndex(qMin(j, audioStreams.count() - 1)); 5849 vh->addWidget(cb); 5850 } 5851 mainLayout->addWidget(streamFrame); 5852 } 5853 mainLayout->addStretch(10); 5854 mainLayout->addWidget(buttonBox); 5855 if (dialog->exec() == QDialog::Accepted) { 5856 // import selected streams 5857 int importedStreams = 0; 5858 for (int i = 0; i < groupList.count(); ++i) { 5859 if (groupList.at(i)->isChecked()) { 5860 int vindex = groupList.at(i)->property("vindex").toInt(); 5861 int ax = qMin(i, comboList.size() - 1); 5862 int aindex = -1; 5863 if (ax >= 0) { 5864 // only check audio index if we have several audio streams 5865 aindex = comboList.at(ax)->itemData(comboList.at(ax)->currentIndex()).toInt(); 5866 } 5867 addStream(vindex, i, aindex, ax, undo, redo); 5868 importedStreams++; 5869 } 5870 } 5871 pCore->pushUndo(undo, redo, i18np("Add additional stream for clip", "Add additional streams for clip", importedStreams)); 5872 } 5873 } 5874 5875 int Bin::getAllClipMarkers(int category) const 5876 { 5877 int markersCount = 0; 5878 QList<std::shared_ptr<ProjectClip>> clipList = m_itemModel->getRootFolder()->childClips(); 5879 for (const std::shared_ptr<ProjectClip> &clip : qAsConst(clipList)) { 5880 markersCount += clip->getMarkerModel()->getAllMarkers(category).count(); 5881 } 5882 return markersCount; 5883 } 5884 5885 void Bin::removeMarkerCategories(QList<int> toRemove, const QMap<int, int> remapCategories) 5886 { 5887 Fun undo = []() { return true; }; 5888 Fun redo = []() { return true; }; 5889 bool found = false; 5890 QList<std::shared_ptr<ProjectClip>> clipList = m_itemModel->getRootFolder()->childClips(); 5891 for (const std::shared_ptr<ProjectClip> &clip : qAsConst(clipList)) { 5892 bool res = clip->removeMarkerCategories(toRemove, remapCategories, undo, redo); 5893 if (!found && res) { 5894 found = true; 5895 } 5896 } 5897 if (found) { 5898 pCore->pushUndo(undo, redo, i18n("Remove clip markers")); 5899 } 5900 } 5901 5902 QStringList Bin::sequenceReferencedClips(const QUuid &uuid) const 5903 { 5904 QStringList results; 5905 QList<std::shared_ptr<ProjectClip>> clipList = m_itemModel->getRootFolder()->childClips(); 5906 for (const std::shared_ptr<ProjectClip> &clip : qAsConst(clipList)) { 5907 if (clip->refCount() > 0) { 5908 const QString referenced = clip->isReferenced(uuid); 5909 if (!referenced.isEmpty()) { 5910 results << referenced; 5911 } 5912 } 5913 } 5914 return results; 5915 } 5916 5917 void Bin::updateSequenceClip(const QUuid &uuid, int duration, int pos) 5918 { 5919 if (pos > -1) { 5920 m_doc->setSequenceProperty(uuid, QStringLiteral("position"), pos); 5921 } 5922 const QString binId = m_itemModel->getSequenceId(uuid); 5923 if (!binId.isEmpty() && m_doc->isModified()) { 5924 std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(binId); 5925 Q_ASSERT(clip != nullptr); 5926 if (m_doc->sequenceThumbRequiresRefresh(uuid)) { 5927 // Store general sequence properties 5928 QMap<QString, QString> properties; 5929 properties.insert(QStringLiteral("length"), QString::number(duration)); 5930 properties.insert(QStringLiteral("out"), QString::number(duration - 1)); 5931 properties.insert(QStringLiteral("kdenlive:duration"), clip->framesToTime(duration)); 5932 properties.insert(QStringLiteral("kdenlive:maxduration"), QString::number(duration)); 5933 clip->setProperties(properties); 5934 // Reset thumbs producer 5935 clip->resetSequenceThumbnails(); 5936 ClipLoadTask::start(ObjectId(KdenliveObjectType::BinClip, binId.toInt(), QUuid()), QDomElement(), true, -1, -1, this); 5937 m_doc->sequenceThumbUpdated(uuid); 5938 clip->reloadTimeline(); 5939 } 5940 } 5941 } 5942 5943 void Bin::updateTimelineOccurrences() 5944 { 5945 // Update the clip in timeline menu 5946 QModelIndex currentSelection = m_proxyModel->selectionModel()->currentIndex(); 5947 bool triggered = false; 5948 if (currentSelection.isValid()) { 5949 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(currentSelection)); 5950 if (currentItem) { 5951 if (currentItem->itemType() == AbstractProjectItem::ClipItem) { 5952 auto clip = std::static_pointer_cast<ProjectClip>(currentItem); 5953 if (clip) { 5954 triggered = true; 5955 Q_EMIT findInTimeline(clip->clipId(), clip->timelineInstances()); 5956 } 5957 } else if (currentItem->itemType() == AbstractProjectItem::FolderItem) { 5958 m_sequencesFolderAction->setChecked(currentItem->clipId().toInt() == m_itemModel->defaultSequencesFolder()); 5959 } 5960 } 5961 } 5962 if (!triggered) { 5963 Q_EMIT findInTimeline(QString()); 5964 } 5965 } 5966 5967 void Bin::setSequenceThumbnail(const QUuid &uuid, int frame) 5968 { 5969 const QString bid = m_itemModel->getSequenceId(uuid); 5970 if (!bid.isEmpty()) { 5971 std::shared_ptr<ProjectClip> sequenceClip = getBinClip(bid); 5972 if (sequenceClip) { 5973 m_doc->setSequenceProperty(uuid, QStringLiteral("thumbnailFrame"), frame); 5974 ClipLoadTask::start(ObjectId(KdenliveObjectType::BinClip, bid.toInt(), QUuid()), QDomElement(), true, -1, -1, this); 5975 } 5976 } 5977 } 5978 5979 void Bin::updateSequenceAVType(const QUuid &uuid, int tracksCount) 5980 { 5981 const QString bId = m_itemModel->getSequenceId(uuid); 5982 if (!bId.isEmpty()) { 5983 std::shared_ptr<ProjectClip> sequenceClip = getBinClip(bId); 5984 if (sequenceClip) { 5985 sequenceClip->refreshTracksState(tracksCount); 5986 } 5987 } 5988 } 5989 5990 void Bin::setDefaultSequenceFolder(bool enable) 5991 { 5992 QModelIndex currentSelection = m_proxyModel->selectionModel()->currentIndex(); 5993 if (currentSelection.isValid()) { 5994 std::shared_ptr<AbstractProjectItem> currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(currentSelection)); 5995 if (currentItem) { 5996 m_itemModel->setSequencesFolder(enable ? currentItem->clipId().toInt() : -1); 5997 } 5998 } 5999 } 6000 6001 void Bin::moveTimeWarpToFolder(const QDir sequenceFolder, bool copy) 6002 { 6003 QList<std::shared_ptr<ProjectClip>> allClips = m_itemModel->getRootFolder()->childClips(); 6004 for (auto &c : allClips) { 6005 c->copyTimeWarpProducers(sequenceFolder, copy); 6006 } 6007 } 6008 6009 void Bin::sequenceActivated() 6010 { 6011 updateTargets(); 6012 QList<std::shared_ptr<ProjectClip>> allClips = m_itemModel->getRootFolder()->childClips(); 6013 for (auto &c : allClips) { 6014 c->refreshBounds(); 6015 } 6016 } 6017 6018 bool Bin::usesVariableFpsClip() 6019 { 6020 QList<std::shared_ptr<ProjectClip>> allClips = m_itemModel->getRootFolder()->childClips(); 6021 for (auto &c : allClips) { 6022 ClipType::ProducerType type = c->clipType(); 6023 if ((type == ClipType::AV || type == ClipType::Video || type == ClipType::Audio) && c->hasVariableFps()) { 6024 return true; 6025 } 6026 } 6027 return false; 6028 } 6029 6030 void Bin::transcodeUsedClips() 6031 { 6032 if (m_doc->isModified()) { 6033 // Recommend saving before the transcode operation 6034 if (KMessageBox::questionTwoActions(QApplication::activeWindow(), 6035 i18nc("@label:textbox", "We recommend that you save the project before the transcode operation"), {}, 6036 KGuiItem(i18nc("@action:button", "Save Project")), 6037 KGuiItem(i18nc("@action:button", "Transcode Without Saving"))) == KMessageBox::PrimaryAction) { 6038 if (!pCore->projectManager()->saveFile()) { 6039 return; 6040 } 6041 } 6042 } 6043 QList<std::shared_ptr<ProjectClip>> allClips = m_itemModel->getRootFolder()->childClips(); 6044 m_proxyModel->selectionModel()->clearSelection(); 6045 // Select all variable fps clips 6046 for (auto &c : allClips) { 6047 ClipType::ProducerType type = c->clipType(); 6048 if ((type == ClipType::AV || type == ClipType::Video || type == ClipType::Audio) && c->hasVariableFps()) { 6049 QModelIndex ix = m_itemModel->getIndexFromItem(c); 6050 int row = ix.row(); 6051 const QModelIndex id = m_itemModel->index(row, 0, ix.parent()); 6052 const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, ix.parent()); 6053 m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), 6054 QItemSelectionModel::Select); 6055 } 6056 } 6057 requestSelectionTranscoding(true); 6058 }