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