diff --git a/src/bin/bin.cpp b/src/bin/bin.cpp index 0fe825d09..0e343d7a9 100644 --- a/src/bin/bin.cpp +++ b/src/bin/bin.cpp @@ -1,3096 +1,3110 @@ /* Copyright (C) 2012 Till Theato Copyright (C) 2014 Jean-Baptiste Mardelle This file is part of Kdenlive. See www.kdenlive.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "bin.h" #include "bincommands.h" #include "clipcreator.hpp" #include "core.h" #include "dialogs/clipcreationdialog.h" #include "doc/documentchecker.h" #include "doc/docundostack.hpp" #include "doc/kdenlivedoc.h" #include "xml/xml.hpp" #include "effects/effectstack/model/effectstackmodel.hpp" #include "jobs/jobmanager.h" #include "jobs/loadjob.hpp" #include "jobs/thumbjob.hpp" #include "jobs/audiothumbjob.hpp" #include "kdenlive_debug.h" #include "kdenlivesettings.h" #include "mainwindow.h" #include "mlt++/Mlt.h" #include "mltcontroller/clipcontroller.h" #include "mltcontroller/clippropertiescontroller.h" #include "monitor/monitor.h" #include "project/dialogs/slideshowclip.h" #include "project/invaliddialog.h" #include "project/projectcommands.h" #include "project/projectmanager.h" #include "projectclip.h" #include "projectfolder.h" #include "projectfolderup.h" #include "projectitemmodel.h" #include "projectsortproxymodel.h" #include "projectsubclip.h" #include "titler/titlewidget.h" #include "ui_qtextclip_ui.h" #include "undohelper.hpp" #include "xml/xml.hpp" #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include /** * @class BinItemDelegate * @brief This class is responsible for drawing items in the QTreeView. */ class BinItemDelegate : public QStyledItemDelegate { public: explicit BinItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) , m_editorOpen(false) , dragType(PlaylistState::Disabled) , m_dar(1.778) { connect(this, &QStyledItemDelegate::closeEditor, [&]() { m_editorOpen = false; }); } void setDar(double dar) { m_dar = dar; } void setEditorData(QWidget *w, const QModelIndex &i) const override { if (!m_editorOpen) { QStyledItemDelegate::setEditorData(w, i); m_editorOpen = true; } } bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *me = (QMouseEvent *)event; if (m_audioDragRect.contains(me->pos())) { dragType = PlaylistState::AudioOnly; } else if (m_videoDragRect.contains(me->pos())) { dragType = PlaylistState::VideoOnly; } else { dragType = PlaylistState::Disabled; } } event->ignore(); return false; } void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (index.column() != 0) { QStyledItemDelegate::updateEditorGeometry(editor, option, index); return; } QStyleOptionViewItem opt = option; initStyleOption(&opt, index); QRect r1 = option.rect; int type = index.data(AbstractProjectItem::ItemTypeRole).toInt(); int decoWidth = 0; if (opt.decorationSize.height() > 0) { decoWidth += r1.height() * m_dar; } int mid = 0; if (type == AbstractProjectItem::ClipItem || type == AbstractProjectItem::SubClipItem) { mid = (int)((r1.height() / 2)); } r1.adjust(decoWidth, 0, 0, -mid); QFont ft = option.font; ft.setBold(true); QFontMetricsF fm(ft); QRect r2 = fm.boundingRect(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString()).toRect(); editor->setGeometry(r2); } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { QSize hint = QStyledItemDelegate::sizeHint(option, index); QString text = index.data(AbstractProjectItem::DataName).toString(); QRectF r = option.rect; QFont ft = option.font; ft.setBold(true); QFontMetricsF fm(ft); QStyle *style = option.widget ? option.widget->style() : QApplication::style(); const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; int width = fm.boundingRect(r, Qt::AlignLeft | Qt::AlignTop, text).width() + option.decorationSize.width() + 2 * textMargin; hint.setWidth(width); int type = index.data(AbstractProjectItem::ItemTypeRole).toInt(); if (type == AbstractProjectItem::FolderItem || type == AbstractProjectItem::FolderUpItem) { return QSize(hint.width(), qMin(option.fontMetrics.lineSpacing() + 4, hint.height())); } if (type == AbstractProjectItem::ClipItem) { return QSize(hint.width(), qMax(option.fontMetrics.lineSpacing() * 2 + 4, qMax(hint.height(), option.decorationSize.height()))); } if (type == AbstractProjectItem::SubClipItem) { return QSize(hint.width(), qMax(option.fontMetrics.lineSpacing() * 2 + 4, qMin(hint.height(), (int)(option.decorationSize.height() / 1.5)))); } QIcon icon = qvariant_cast(index.data(Qt::DecorationRole)); QString line1 = index.data(Qt::DisplayRole).toString(); QString line2 = index.data(Qt::UserRole).toString(); int textW = qMax(option.fontMetrics.width(line1), option.fontMetrics.width(line2)); QSize iconSize = icon.actualSize(option.decorationSize); return QSize(qMax(textW, iconSize.width()) + 4, option.fontMetrics.lineSpacing() * 2 + 4); } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (index.column() == 0 && !index.data().isNull()) { QRect r1 = option.rect; painter->save(); painter->setClipRect(r1); QStyleOptionViewItem opt(option); initStyleOption(&opt, index); int type = index.data(AbstractProjectItem::ItemTypeRole).toInt(); QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; // QRect r = QStyle::alignedRect(opt.direction, Qt::AlignVCenter | Qt::AlignLeft, opt.decorationSize, r1); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); if ((option.state & static_cast(QStyle::State_Selected)) != 0) { painter->setPen(option.palette.highlightedText().color()); } else { painter->setPen(option.palette.text().color()); } QRect r = r1; QFont font = painter->font(); font.setBold(true); painter->setFont(font); if (type == AbstractProjectItem::ClipItem || type == AbstractProjectItem::SubClipItem) { int decoWidth = 0; if (opt.decorationSize.height() > 0) { r.setWidth(opt.decorationSize.width()); QPixmap pix = opt.icon.pixmap(r.size()); // Draw icon painter->drawPixmap(r.topLeft() + QPoint(0, (r.height() - pix.height()) / 2), pix); decoWidth += r.height() * m_dar; } int mid = (int)((r1.height() / 2)); r1.adjust(decoWidth, 0, 0, -mid); QRect r2 = option.rect; r2.adjust(decoWidth, mid, 0, 0); QRectF bounding; painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString(), &bounding); font.setBold(false); painter->setFont(font); QString subText = index.data(AbstractProjectItem::DataDuration).toString(); if (!subText.isEmpty()) { r2.adjust(0, bounding.bottom() - r2.top(), 0, 0); QColor subTextColor = painter->pen().color(); subTextColor.setAlphaF(.5); painter->setPen(subTextColor); // Draw usage counter int usage = index.data(AbstractProjectItem::UsageCount).toInt(); if (usage > 0) { subText.append(QString().sprintf(" [%d]", usage)); } painter->drawText(r2, Qt::AlignLeft | Qt::AlignTop, subText, &bounding); // Add audio/video icons for selective drag int cType = index.data(AbstractProjectItem::ClipType).toInt(); bool hasAudioAndVideo = index.data(AbstractProjectItem::ClipHasAudioAndVideo).toBool(); if (hasAudioAndVideo && (cType == ClipType::AV || cType == ClipType::Playlist) && (opt.state & QStyle::State_MouseOver)) { bounding.moveLeft(bounding.right() + (2 * textMargin)); bounding.adjust(0, textMargin, 0, -textMargin); QIcon aDrag = QIcon::fromTheme(QStringLiteral("audio-volume-medium")); m_audioDragRect = bounding.toRect(); m_audioDragRect.setWidth(m_audioDragRect.height()); aDrag.paint(painter, m_audioDragRect, Qt::AlignLeft); m_videoDragRect = m_audioDragRect; m_videoDragRect.moveLeft(m_audioDragRect.right()); QIcon vDrag = QIcon::fromTheme(QStringLiteral("kdenlive-show-video")); vDrag.paint(painter, m_videoDragRect, Qt::AlignLeft); } else { m_audioDragRect = QRect(); m_videoDragRect = QRect(); } } if (type == AbstractProjectItem::ClipItem) { // Overlay icon if necessary QVariant v = index.data(AbstractProjectItem::IconOverlay); if (!v.isNull()) { QIcon reload = QIcon::fromTheme(v.toString()); r.setTop(r.bottom() - bounding.height()); r.setWidth(bounding.height()); reload.paint(painter, r); } int jobProgress = index.data(AbstractProjectItem::JobProgress).toInt(); JobManagerStatus status = index.data(AbstractProjectItem::JobStatus).value(); if (status == JobManagerStatus::Pending || status == JobManagerStatus::Running) { // Draw job progress bar int progressWidth = option.fontMetrics.averageCharWidth() * 8; int progressHeight = option.fontMetrics.ascent() / 4; QRect progress(r1.x() + 1, opt.rect.bottom() - progressHeight - 2, progressWidth, progressHeight); painter->setPen(Qt::NoPen); painter->setBrush(Qt::darkGray); if (status == JobManagerStatus::Running) { painter->drawRoundedRect(progress, 2, 2); painter->setBrush((option.state & static_cast((QStyle::State_Selected) != 0)) != 0 ? option.palette.text() : option.palette.highlight()); progress.setWidth((progressWidth - 2) * jobProgress / 100); painter->drawRoundedRect(progress, 2, 2); } else { // Draw kind of a pause icon progress.setWidth(3); painter->drawRect(progress); progress.moveLeft(progress.right() + 3); painter->drawRect(progress); } } bool jobsucceeded = index.data(AbstractProjectItem::JobSuccess).toBool(); if (!jobsucceeded) { QIcon warning = QIcon::fromTheme(QStringLiteral("process-stop")); warning.paint(painter, r2); } } } else { // Folder or Folder Up items int decoWidth = 0; if (opt.decorationSize.height() > 0) { r.setWidth(opt.decorationSize.width()); QPixmap pix = opt.icon.pixmap(r.size()); // Draw icon painter->drawPixmap(r.topLeft() + QPoint(0, (r.height() - pix.height()) / 2), pix); decoWidth += r.height() * m_dar; } r1.adjust(decoWidth, 0, 0, 0); QRectF bounding; painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString(), &bounding); } painter->restore(); } else { QStyledItemDelegate::paint(painter, option, index); } } private: mutable bool m_editorOpen; mutable QRect m_audioDragRect; mutable QRect m_videoDragRect; double m_dar; public: PlaylistState::ClipState dragType; }; MyListView::MyListView(QWidget *parent) : QListView(parent) { setViewMode(QListView::IconMode); setMovement(QListView::Static); setResizeMode(QListView::Adjust); setUniformItemSizes(true); setDragDropMode(QAbstractItemView::DragDrop); setAcceptDrops(true); setDragEnabled(true); viewport()->setAcceptDrops(true); } void MyListView::focusInEvent(QFocusEvent *event) { QListView::focusInEvent(event); if (event->reason() == Qt::MouseFocusReason) { emit focusView(); } } MyTreeView::MyTreeView(QWidget *parent) : QTreeView(parent) { setEditing(false); } void MyTreeView::mousePressEvent(QMouseEvent *event) { QTreeView::mousePressEvent(event); if (event->button() == Qt::LeftButton) { m_startPos = event->pos(); QModelIndex ix = indexAt(m_startPos); if (ix.isValid()) { QAbstractItemDelegate *del = itemDelegate(ix); m_dragType = static_cast(del)->dragType; } else { m_dragType = PlaylistState::Disabled; } } } void MyTreeView::focusInEvent(QFocusEvent *event) { QTreeView::focusInEvent(event); if (event->reason() == Qt::MouseFocusReason) { emit focusView(); } } void MyTreeView::mouseMoveEvent(QMouseEvent *event) { bool dragged = false; if ((event->buttons() & Qt::LeftButton) != 0u) { int distance = (event->pos() - m_startPos).manhattanLength(); if (distance >= QApplication::startDragDistance()) { dragged = performDrag(); } } if (!dragged) { QTreeView::mouseMoveEvent(event); } } void MyTreeView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) { QAbstractItemView::closeEditor(editor, hint); setEditing(false); } void MyTreeView::editorDestroyed(QObject *editor) { QAbstractItemView::editorDestroyed(editor); setEditing(false); } void MyTreeView::keyPressEvent(QKeyEvent *event) { if (isEditing()) { QTreeView::keyPressEvent(event); return; } QModelIndex currentIndex = this->currentIndex(); if (event->key() == Qt::Key_Return && currentIndex.isValid()) { if (this->isExpanded(currentIndex)) { this->collapse(currentIndex); } else { this->expand(currentIndex); } } QTreeView::keyPressEvent(event); } bool MyTreeView::isEditing() const { return state() == QAbstractItemView::EditingState; } void MyTreeView::setEditing(bool edit) { setState(edit ? QAbstractItemView::EditingState : QAbstractItemView::NoState); } bool MyTreeView::performDrag() { QModelIndexList bases = selectedIndexes(); QModelIndexList indexes; for (int i = 0; i < bases.count(); i++) { if (bases.at(i).column() == 0) { indexes << bases.at(i); } } if (indexes.isEmpty()) { return false; } // Check if we want audio or video only emit updateDragMode(m_dragType); auto *drag = new QDrag(this); drag->setMimeData(model()->mimeData(indexes)); QModelIndex ix = indexes.constFirst(); if (ix.isValid()) { QIcon icon = ix.data(AbstractProjectItem::DataThumbnail).value(); QPixmap pix = icon.pixmap(iconSize()); QSize size = pix.size(); QImage image(size, QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); QPainter p(&image); p.setOpacity(0.7); p.drawPixmap(0, 0, pix); p.setOpacity(1); if (indexes.count() > 1) { QPalette palette; int radius = size.height() / 3; p.setBrush(palette.highlight()); p.setPen(palette.highlightedText().color()); p.drawEllipse(QPoint(size.width() / 2, size.height() / 2), radius, radius); p.drawText(size.width() / 2 - radius, size.height() / 2 - radius, 2 * radius, 2 * radius, Qt::AlignCenter, QString::number(indexes.count())); } p.end(); drag->setPixmap(QPixmap::fromImage(image)); } drag->exec(); return true; } SmallJobLabel::SmallJobLabel(QWidget *parent) : QPushButton(parent) , m_action(nullptr) { setFixedWidth(0); setFlat(true); m_timeLine = new QTimeLine(500, this); QObject::connect(m_timeLine, &QTimeLine::valueChanged, this, &SmallJobLabel::slotTimeLineChanged); QObject::connect(m_timeLine, &QTimeLine::finished, this, &SmallJobLabel::slotTimeLineFinished); hide(); } const QString SmallJobLabel::getStyleSheet(const QPalette &p) { KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window); QColor bg = scheme.background(KColorScheme::LinkBackground).color(); QColor fg = scheme.foreground(KColorScheme::LinkText).color(); QString style = QStringLiteral("QPushButton {margin:3px;padding:2px;background-color: rgb(%1, %2, %3);border-radius: 4px;border: none;color: rgb(%4, %5, %6)}") .arg(bg.red()) .arg(bg.green()) .arg(bg.blue()) .arg(fg.red()) .arg(fg.green()) .arg(fg.blue()); bg = scheme.background(KColorScheme::ActiveBackground).color(); fg = scheme.foreground(KColorScheme::ActiveText).color(); style.append( QStringLiteral("\nQPushButton:hover {margin:3px;padding:2px;background-color: rgb(%1, %2, %3);border-radius: 4px;border: none;color: rgb(%4, %5, %6)}") .arg(bg.red()) .arg(bg.green()) .arg(bg.blue()) .arg(fg.red()) .arg(fg.green()) .arg(fg.blue())); return style; } void SmallJobLabel::setAction(QAction *action) { m_action = action; } void SmallJobLabel::slotTimeLineChanged(qreal value) { setFixedWidth(qMin(value * 2, qreal(1.0)) * sizeHint().width()); update(); } void SmallJobLabel::slotTimeLineFinished() { if (m_timeLine->direction() == QTimeLine::Forward) { // Show m_action->setVisible(true); } else { // Hide m_action->setVisible(false); setText(QString()); } } void SmallJobLabel::slotSetJobCount(int jobCount) { if (jobCount > 0) { // prepare animation setText(i18np("%1 job", "%1 jobs", jobCount)); setToolTip(i18np("%1 pending job", "%1 pending jobs", jobCount)); if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this) != 0) { setFixedWidth(sizeHint().width()); m_action->setVisible(true); return; } if (m_action->isVisible()) { setFixedWidth(sizeHint().width()); update(); return; } setFixedWidth(0); m_action->setVisible(true); int wantedWidth = sizeHint().width(); setGeometry(-wantedWidth, 0, wantedWidth, height()); m_timeLine->setDirection(QTimeLine::Forward); if (m_timeLine->state() == QTimeLine::NotRunning) { m_timeLine->start(); } } else { if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this) != 0) { setFixedWidth(0); m_action->setVisible(false); return; } // hide m_timeLine->setDirection(QTimeLine::Backward); if (m_timeLine->state() == QTimeLine::NotRunning) { m_timeLine->start(); } } } LineEventEater::LineEventEater(QObject *parent) : QObject(parent) { } bool LineEventEater::eventFilter(QObject *obj, QEvent *event) { switch (event->type()) { case QEvent::ShortcutOverride: if (((QKeyEvent *)event)->key() == Qt::Key_Escape) { emit clearSearchLine(); } break; case QEvent::Resize: // Workaround Qt BUG 54676 emit showClearButton(((QResizeEvent *)event)->size().width() > QFontMetrics(QApplication::font()).averageCharWidth() * 8); break; default: break; } return QObject::eventFilter(obj, event); } Bin::Bin(const std::shared_ptr &model, QWidget *parent) : QWidget(parent) , isLoading(false) , m_itemModel(model) , m_itemView(nullptr) , m_doc(nullptr) , m_extractAudioAction(nullptr) , m_transcodeAction(nullptr) , m_clipsActionsMenu(nullptr) , m_inTimelineAction(nullptr) , m_listType((BinViewType)KdenliveSettings::binMode()) , m_iconSize(160, 90) , m_propertiesPanel(nullptr) , m_blankThumb() , m_invalidClipDialog(nullptr) , m_gainedFocus(false) , m_audioDuration(0) , m_processedAudio(0) { m_layout = new QVBoxLayout(this); // Create toolbar for buttons m_toolbar = new QToolBar(this); int size = style()->pixelMetric(QStyle::PM_SmallIconSize); QSize iconSize(size, size); m_toolbar->setIconSize(iconSize); m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); m_layout->addWidget(m_toolbar); m_layout->setSpacing(0); m_layout->setContentsMargins(0, 0, 0, 0); // Search line m_proxyModel = new ProjectSortProxyModel(this); m_proxyModel->setDynamicSortFilter(true); m_searchLine = new QLineEdit(this); m_searchLine->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); // m_searchLine->setClearButtonEnabled(true); m_searchLine->setPlaceholderText(i18n("Search")); m_searchLine->setFocusPolicy(Qt::ClickFocus); connect(m_searchLine, &QLineEdit::textChanged, m_proxyModel, &ProjectSortProxyModel::slotSetSearchString); auto *leventEater = new LineEventEater(this); m_searchLine->installEventFilter(leventEater); connect(leventEater, &LineEventEater::clearSearchLine, m_searchLine, &QLineEdit::clear); connect(leventEater, &LineEventEater::showClearButton, this, &Bin::showClearButton); setFocusPolicy(Qt::ClickFocus); connect(m_itemModel.get(), &ProjectItemModel::refreshPanel, this, &Bin::refreshPanel); connect(m_itemModel.get(), &ProjectItemModel::refreshAudioThumbs, this, &Bin::doRefreshAudioThumbs); connect(m_itemModel.get(), &ProjectItemModel::refreshClip, this, &Bin::refreshClip); connect(m_itemModel.get(), &ProjectItemModel::updateTimelineProducers, this, &Bin::updateTimelineProducers); connect(m_itemModel.get(), &ProjectItemModel::emitMessage, this, &Bin::emitMessage); // Connect models m_proxyModel->setSourceModel(m_itemModel.get()); connect(m_itemModel.get(), &QAbstractItemModel::dataChanged, m_proxyModel, &ProjectSortProxyModel::slotDataChanged); connect(m_proxyModel, &ProjectSortProxyModel::selectModel, this, &Bin::selectProxyModel); connect(m_itemModel.get(), static_cast(&ProjectItemModel::itemDropped), this, static_cast(&Bin::slotItemDropped)); connect(m_itemModel.get(), static_cast &, const QModelIndex &)>(&ProjectItemModel::itemDropped), this, static_cast &, const QModelIndex &)>(&Bin::slotItemDropped)); connect(m_itemModel.get(), &ProjectItemModel::effectDropped, this, &Bin::slotEffectDropped); connect(m_itemModel.get(), &QAbstractItemModel::dataChanged, this, &Bin::slotItemEdited); connect(this, &Bin::refreshPanel, this, &Bin::doRefreshPanel); // Zoom slider QWidget *container = new QWidget(this); QHBoxLayout *lay = new QHBoxLayout; m_slider = new QSlider(Qt::Horizontal, this); m_slider->setMaximumWidth(100); m_slider->setMinimumWidth(40); m_slider->setRange(0, 10); m_slider->setValue(KdenliveSettings::bin_zoom()); connect(m_slider, &QAbstractSlider::valueChanged, this, &Bin::slotSetIconSize); QToolButton *tb1 = new QToolButton(this); tb1->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); connect(tb1, &QToolButton::clicked, [&] () { m_slider->setValue(qMin(m_slider->value() + 1, m_slider->maximum())); }); QToolButton *tb2 = new QToolButton(this); tb2->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); connect(tb2, &QToolButton::clicked, [&] () { m_slider->setValue(qMax(m_slider->value() - 1, m_slider->minimum())); }); lay->addWidget(tb1); lay->addWidget(m_slider); lay->addWidget(tb2); container->setLayout(lay); auto *widgetslider = new QWidgetAction(this); widgetslider->setDefaultWidget(container); // View type KSelectAction *listType = new KSelectAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18n("View Mode"), this); pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode"), listType); QAction *treeViewAction = listType->addAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18n("Tree View")); listType->addAction(treeViewAction); treeViewAction->setData(BinTreeView); if (m_listType == treeViewAction->data().toInt()) { listType->setCurrentAction(treeViewAction); } pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode_tree"), treeViewAction); QAction *iconViewAction = listType->addAction(QIcon::fromTheme(QStringLiteral("view-list-icons")), i18n("Icon View")); iconViewAction->setData(BinIconView); if (m_listType == iconViewAction->data().toInt()) { listType->setCurrentAction(iconViewAction); } pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode_icon"), iconViewAction); QAction *disableEffects = new QAction(i18n("Disable Bin Effects"), this); connect(disableEffects, &QAction::triggered, [this](bool disable) { this->setBinEffectsEnabled(!disable); }); disableEffects->setIcon(QIcon::fromTheme(QStringLiteral("favorite"))); disableEffects->setData("disable_bin_effects"); disableEffects->setCheckable(true); disableEffects->setChecked(false); pCore->window()->actionCollection()->addAction(QStringLiteral("disable_bin_effects"), disableEffects); m_renameAction = KStandardAction::renameFile(this, SLOT(slotRenameItem()), this); m_renameAction->setText(i18n("Rename")); m_renameAction->setData("rename"); pCore->window()->actionCollection()->addAction(QStringLiteral("rename"), m_renameAction); listType->setToolBarMode(KSelectAction::MenuMode); connect(listType, static_cast(&KSelectAction::triggered), this, &Bin::slotInitView); // Settings menu QMenu *settingsMenu = new QMenu(i18n("Settings"), this); settingsMenu->addAction(listType); QMenu *sliderMenu = new QMenu(i18n("Zoom"), this); sliderMenu->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); sliderMenu->addAction(widgetslider); settingsMenu->addMenu(sliderMenu); // Column show / hide actions m_showDate = new QAction(i18n("Show date"), this); m_showDate->setCheckable(true); connect(m_showDate, &QAction::triggered, this, &Bin::slotShowDateColumn); m_showDesc = new QAction(i18n("Show description"), this); m_showDesc->setCheckable(true); connect(m_showDesc, &QAction::triggered, this, &Bin::slotShowDescColumn); settingsMenu->addAction(m_showDate); settingsMenu->addAction(m_showDesc); settingsMenu->addAction(disableEffects); auto *button = new QToolButton; button->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu"))); button->setToolTip(i18n("Options")); button->setMenu(settingsMenu); button->setPopupMode(QToolButton::InstantPopup); m_toolbar->addWidget(button); // small info button for pending jobs m_infoLabel = new SmallJobLabel(this); m_infoLabel->setStyleSheet(SmallJobLabel::getStyleSheet(palette())); connect(pCore->jobManager().get(), &JobManager::jobCount, m_infoLabel, &SmallJobLabel::slotSetJobCount); QAction *infoAction = m_toolbar->addWidget(m_infoLabel); m_jobsMenu = new QMenu(this); // connect(m_jobsMenu, &QMenu::aboutToShow, this, &Bin::slotPrepareJobsMenu); m_cancelJobs = new QAction(i18n("Cancel All Jobs"), this); m_cancelJobs->setCheckable(false); m_discardCurrentClipJobs = new QAction(i18n("Cancel Current Clip Jobs"), this); m_discardCurrentClipJobs->setCheckable(false); m_discardPendingJobs = new QAction(i18n("Cancel Pending Jobs"), this); m_discardPendingJobs->setCheckable(false); m_jobsMenu->addAction(m_cancelJobs); m_jobsMenu->addAction(m_discardCurrentClipJobs); m_jobsMenu->addAction(m_discardPendingJobs); m_infoLabel->setMenu(m_jobsMenu); m_infoLabel->setAction(infoAction); // Hack, create toolbar spacer QWidget *spacer = new QWidget(); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_toolbar->addWidget(spacer); // Add search line m_toolbar->addWidget(m_searchLine); m_binTreeViewDelegate = new BinItemDelegate(this); // connect(pCore->projectManager(), SIGNAL(projectOpened(Project*)), this, SLOT(setProject(Project*))); m_headerInfo = QByteArray::fromBase64(KdenliveSettings::treeviewheaders().toLatin1()); m_propertiesPanel = new QScrollArea(this); m_propertiesPanel->setFrameShape(QFrame::NoFrame); // Info widget for failed jobs, other errors m_infoMessage = new KMessageWidget(this); m_layout->addWidget(m_infoMessage); m_infoMessage->setCloseButtonVisible(false); connect(m_infoMessage, &KMessageWidget::hideAnimationFinished, this, &Bin::slotResetInfoMessage); // m_infoMessage->setWordWrap(true); m_infoMessage->hide(); connect(this, &Bin::requesteInvalidRemoval, this, &Bin::slotQueryRemoval); connect(this, SIGNAL(displayBinMessage(QString, KMessageWidget::MessageType)), this, SLOT(doDisplayMessage(QString, KMessageWidget::MessageType))); } Bin::~Bin() { blockSignals(true); m_proxyModel->selectionModel()->blockSignals(true); setEnabled(false); m_propertiesPanel = nullptr; abortOperations(); m_itemModel->clean(); } QDockWidget *Bin::clipPropertiesDock() { return m_propertiesDock; } void Bin::abortOperations() { blockSignals(true); if (m_propertiesPanel) { for (QWidget *w : m_propertiesPanel->findChildren()) { delete w; } } delete m_itemView; m_itemView = nullptr; blockSignals(false); } bool Bin::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::MouseButtonRelease) { if (!m_monitor->isActive()) { m_monitor->slotActivateMonitor(); } bool success = QWidget::eventFilter(obj, event); if (m_gainedFocus) { QMouseEvent *mouseEvent = static_cast(event); QAbstractItemView *view = qobject_cast(obj->parent()); if (view) { QModelIndex idx = view->indexAt(mouseEvent->pos()); m_gainedFocus = false; if (idx.isValid()) { std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(idx)); editMasterEffect(item); } else { editMasterEffect(nullptr); } } // make sure we discard the focus indicator m_gainedFocus = false; } return success; } if (event->type() == QEvent::MouseButtonDblClick) { QMouseEvent *mouseEvent = static_cast(event); QAbstractItemView *view = qobject_cast(obj->parent()); if (view) { QModelIndex idx = view->indexAt(mouseEvent->pos()); if (!idx.isValid()) { // User double clicked on empty area slotAddClip(); } else { slotItemDoubleClicked(idx, mouseEvent->pos()); } } else { qCDebug(KDENLIVE_LOG) << " +++++++ NO VIEW-------!!"; } return true; } if (event->type() == QEvent::Wheel) { QWheelEvent *e = static_cast(event); if ((e != nullptr) && e->modifiers() == Qt::ControlModifier) { slotZoomView(e->delta() > 0); // emit zoomView(e->delta() > 0); return true; } } return QWidget::eventFilter(obj, event); } void Bin::refreshIcons() { QList allMenus = this->findChildren(); for (int i = 0; i < allMenus.count(); i++) { QMenu *m = allMenus.at(i); QIcon ic = m->icon(); if (ic.isNull() || ic.name().isEmpty()) { continue; } QIcon newIcon = QIcon::fromTheme(ic.name()); m->setIcon(newIcon); } QList allButtons = this->findChildren(); for (int i = 0; i < allButtons.count(); i++) { QToolButton *m = allButtons.at(i); QIcon ic = m->icon(); if (ic.isNull() || ic.name().isEmpty()) { continue; } QIcon newIcon = QIcon::fromTheme(ic.name()); m->setIcon(newIcon); } } void Bin::slotSaveHeaders() { if ((m_itemView != nullptr) && m_listType == BinTreeView) { // save current treeview state (column width) QTreeView *view = static_cast(m_itemView); m_headerInfo = view->header()->saveState(); KdenliveSettings::setTreeviewheaders(m_headerInfo.toBase64()); } } void Bin::slotZoomView(bool zoomIn) { if (m_itemModel->rowCount() == 0) { // Don't zoom on empty bin return; } int progress = (zoomIn) ? 1 : -1; m_slider->setValue(m_slider->value() + progress); } Monitor *Bin::monitor() { return m_monitor; } const QStringList Bin::getFolderInfo(const QModelIndex &selectedIx) { QModelIndexList indexes; if (selectedIx.isValid()) { indexes << selectedIx; } else { indexes = m_proxyModel->selectionModel()->selectedIndexes(); } if (indexes.isEmpty()) { // return root folder info QStringList folderInfo; folderInfo << QString::number(-1); folderInfo << QString(); return folderInfo; } QModelIndex ix = indexes.constFirst(); if (ix.isValid() && (m_proxyModel->selectionModel()->isSelected(ix) || selectedIx.isValid())) { return m_itemModel->getEnclosingFolderInfo(m_proxyModel->mapToSource(ix)); } // return root folder info QStringList folderInfo; folderInfo << QString::number(-1); folderInfo << QString(); return folderInfo; } void Bin::slotAddClip() { // Check if we are in a folder QString parentFolder = getCurrentFolder(); ClipCreationDialog::createClipsCommand(m_doc, parentFolder, m_itemModel); } std::shared_ptr Bin::getFirstSelectedClip() { const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { return std::shared_ptr(); } for (const QModelIndex &ix : indexes) { std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); if (item->itemType() == AbstractProjectItem::ClipItem) { auto clip = std::static_pointer_cast(item); if (clip) { return clip; } } } return nullptr; } void Bin::slotDeleteClip() { const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); std::vector> items; bool included = false; bool usedFolder = false; auto checkInclusion = [](bool accum, std::shared_ptr item) { return accum || std::static_pointer_cast(item)->isIncludedInTimeline(); }; for (const QModelIndex &ix : indexes) { if (!ix.isValid() || ix.column() != 0) { continue; } std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); if (!item) { qDebug() << "Suspicious: item not found when trying to delete"; continue; } included = included || item->accumulate(false, checkInclusion); // Check if we are deleting non-empty folders: usedFolder = usedFolder || item->childCount() > 0; items.push_back(item); } if (included && (KMessageBox::warningContinueCancel(this, i18n("This will delete all selected clips from timeline")) != KMessageBox::Continue)) { return; } if (usedFolder && (KMessageBox::warningContinueCancel(this, i18n("This will delete all folder content")) != KMessageBox::Continue)) { return; } Fun undo = []() { return true; }; Fun redo = []() { return true; }; for (const auto &item : items) { m_itemModel->requestBinClipDeletion(item, undo, redo); } pCore->pushUndo(undo, redo, i18n("Delete bin Clips")); } void Bin::slotReloadClip() { const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); for (const QModelIndex &ix : indexes) { if (!ix.isValid() || ix.column() != 0) { continue; } std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); auto currentItem = std::static_pointer_cast(item); if (currentItem) { emit openClip(std::shared_ptr()); if (currentItem->clipType() == ClipType::Playlist) { // Check if a clip inside playlist is missing QString path = currentItem->url(); QFile f(path); QDomDocument doc; doc.setContent(&f, false); f.close(); DocumentChecker d(QUrl::fromLocalFile(path), doc); if (!d.hasErrorInClips() && doc.documentElement().hasAttribute(QStringLiteral("modified"))) { QString backupFile = path + QStringLiteral(".backup"); KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(path), QUrl::fromLocalFile(backupFile)); if (copyjob->exec()) { if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { KMessageBox::sorry(this, i18n("Unable to write to file %1", path)); } else { QTextStream out(&f); out << doc.toString(); f.close(); KMessageBox::information( this, i18n("Your project file was modified by Kdenlive.\nTo make sure you don't lose data, a backup copy called %1 was created.", backupFile)); } } } } currentItem->reloadProducer(false); } } } void Bin::slotLocateClip() { const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); for (const QModelIndex &ix : indexes) { if (!ix.isValid() || ix.column() != 0) { continue; } std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); auto currentItem = std::static_pointer_cast(item); if (currentItem) { QUrl url = QUrl::fromLocalFile(currentItem->url()).adjusted(QUrl::RemoveFilename); bool exists = QFile(url.toLocalFile()).exists(); if (currentItem->hasUrl() && exists) { QDesktopServices::openUrl(url); qCDebug(KDENLIVE_LOG) << " / / " + url.toString(); } else { if (!exists) { emitMessage(i18n("Couldn't locate ") + QString(" (" + url.toString() + QLatin1Char(')')), 100, ErrorMessage); } return; } } } } void Bin::slotDuplicateClip() { const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); for (const QModelIndex &ix : indexes) { if (!ix.isValid() || ix.column() != 0) { continue; } std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); auto currentItem = std::static_pointer_cast(item); if (currentItem) { QDomDocument doc; QDomElement xml = currentItem->toXml(doc); if (!xml.isNull()) { QString currentName = Xml::getXmlProperty(xml, QStringLiteral("kdenlive:clipname")); if (currentName.isEmpty()) { QUrl url = QUrl::fromLocalFile(Xml::getXmlProperty(xml, QStringLiteral("resource"))); if (url.isValid()) { currentName = url.fileName(); } } if (!currentName.isEmpty()) { currentName.append(i18nc("append to clip name to indicate a copied idem", " (copy)")); Xml::setXmlProperty(xml, QStringLiteral("kdenlive:clipname"), currentName); } QString id; m_itemModel->requestAddBinClip(id, xml, item->parent()->clipId(), i18n("Duplicate clip")); } } } } void Bin::setMonitor(Monitor *monitor) { m_monitor = monitor; connect(m_monitor, &Monitor::addClipToProject, this, &Bin::slotAddClipToProject); connect(m_monitor, &Monitor::refreshCurrentClip, this, &Bin::slotOpenCurrent); connect(this, &Bin::openClip, [&](std::shared_ptr clip, int in, int out) { m_monitor->slotOpenClip(clip, in, out); }); } void Bin::setDocument(KdenliveDoc *project) { m_infoMessage->hide(); blockSignals(true); m_proxyModel->selectionModel()->blockSignals(true); setEnabled(false); // Cleanup previous project m_itemModel->clean(); delete m_itemView; m_itemView = nullptr; m_doc = project; int iconHeight = QFontInfo(font()).pixelSize() * 3.5; m_iconSize = QSize(iconHeight * pCore->getCurrentDar(), iconHeight); setEnabled(true); blockSignals(false); m_proxyModel->selectionModel()->blockSignals(false); connect(m_proxyAction, SIGNAL(toggled(bool)), m_doc, SLOT(slotProxyCurrentItem(bool))); // connect(m_itemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), m_itemView // connect(m_itemModel, SIGNAL(updateCurrentItem()), this, SLOT(autoSelect())); slotInitView(nullptr); bool binEffectsDisabled = getDocumentProperty(QStringLiteral("disablebineffects")).toInt() == 1; setBinEffectsEnabled(!binEffectsDisabled); } void Bin::createClip(const QDomElement &xml) { // Check if clip should be in a folder QString groupId = ProjectClip::getXmlProperty(xml, QStringLiteral("kdenlive:folderid")); std::shared_ptr parentFolder = m_itemModel->getFolderByBinId(groupId); if (!parentFolder) { parentFolder = m_itemModel->getRootFolder(); } QString path = Xml::getXmlProperty(xml, QStringLiteral("resource")); if (path.endsWith(QStringLiteral(".mlt")) || path.endsWith(QStringLiteral(".kdenlive"))) { QFile f(path); QDomDocument doc; doc.setContent(&f, false); f.close(); DocumentChecker d(QUrl::fromLocalFile(path), doc); if (!d.hasErrorInClips() && doc.documentElement().hasAttribute(QStringLiteral("modified"))) { QString backupFile = path + QStringLiteral(".backup"); KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(path), QUrl::fromLocalFile(backupFile)); if (copyjob->exec()) { if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { KMessageBox::sorry(this, i18n("Unable to write to file %1", path)); } else { QTextStream out(&f); out << doc.toString(); f.close(); KMessageBox::information( this, i18n("Your project file was modified by Kdenlive.\nTo make sure you don't lose data, a backup copy called %1 was created.", backupFile)); } } } } QString id = Xml::getTagContentByAttribute(xml, QStringLiteral("property"), QStringLiteral("name"), QStringLiteral("kdenlive:id")); if (id.isEmpty()) { id = QString::number(m_itemModel->getFreeClipId()); } auto newClip = ProjectClip::construct(id, xml, m_blankThumb, m_itemModel); parentFolder->appendChild(newClip); } QString Bin::slotAddFolder(const QString &folderName) { auto parentFolder = m_itemModel->getFolderByBinId(getCurrentFolder()); qDebug() << "pranteforder id" << parentFolder->clipId(); QString newId; Fun undo = []() { return true; }; Fun redo = []() { return true; }; m_itemModel->requestAddFolder(newId, folderName.isEmpty() ? i18n("Folder") : folderName, parentFolder->clipId(), undo, redo); pCore->pushUndo(undo, redo, i18n("Create bin folder")); // Edit folder name if (!folderName.isEmpty()) { // We already have a name, no need to edit return newId; } auto folder = m_itemModel->getFolderByBinId(newId); auto ix = m_itemModel->getIndexFromItem(folder); qDebug() << "selecting" << ix; if (ix.isValid()) { qDebug() << "ix valid"; m_proxyModel->selectionModel()->clearSelection(); int row = ix.row(); const QModelIndex id = m_itemModel->index(row, 0, ix.parent()); const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, ix.parent()); if (id.isValid() && id2.isValid()) { m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), QItemSelectionModel::Select); } m_itemView->edit(m_proxyModel->mapFromSource(ix)); } return newId; } QModelIndex Bin::getIndexForId(const QString &id, bool folderWanted) const { QModelIndexList items = m_itemModel->match(m_itemModel->index(0, 0), AbstractProjectItem::DataId, QVariant::fromValue(id), 2, Qt::MatchRecursive); for (int i = 0; i < items.count(); i++) { AbstractProjectItem *currentItem = static_cast(items.at(i).internalPointer()); AbstractProjectItem::PROJECTITEMTYPE type = currentItem->itemType(); if (folderWanted && type == AbstractProjectItem::FolderItem) { // We found our folder return items.at(i); } if (!folderWanted && type == AbstractProjectItem::ClipItem) { // We found our clip return items.at(i); } } return QModelIndex(); } void Bin::selectClipById(const QString &clipId, int frame, const QPoint &zone) { if (m_monitor->activeClipId() == clipId) { if (frame > -1) { m_monitor->slotSeek(frame); } if (!zone.isNull()) { m_monitor->slotLoadClipZone(zone); } return; } m_proxyModel->selectionModel()->clearSelection(); std::shared_ptr clip = getBinClip(clipId); if (clip) { selectClip(clip); if (frame > -1) { m_monitor->slotSeek(frame); } if (!zone.isNull()) { m_monitor->slotLoadClipZone(zone); } } } void Bin::selectProxyModel(const QModelIndex &id) { if (isLoading) { // return; } if (id.isValid()) { if (id.column() != 0) { return; } std::shared_ptr currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(id)); if (currentItem) { // Set item as current so that it displays its content in clip monitor setCurrent(currentItem); if (currentItem->itemType() == AbstractProjectItem::ClipItem) { m_reloadAction->setEnabled(true); m_locateAction->setEnabled(true); m_duplicateAction->setEnabled(true); std::shared_ptr clip = std::static_pointer_cast(currentItem); ClipType::ProducerType type = clip->clipType(); m_openAction->setEnabled(type == ClipType::Image || type == ClipType::Audio || type == ClipType::Text || type == ClipType::TextTemplate); showClipProperties(clip, false); m_deleteAction->setText(i18n("Delete Clip")); m_proxyAction->setText(i18n("Proxy Clip")); emit findInTimeline(clip->clipId(), clip->timelineInstances()); } else if (currentItem->itemType() == AbstractProjectItem::FolderItem) { // A folder was selected, disable editing clip m_openAction->setEnabled(false); m_reloadAction->setEnabled(false); m_locateAction->setEnabled(false); m_duplicateAction->setEnabled(false); m_deleteAction->setText(i18n("Delete Folder")); m_proxyAction->setText(i18n("Proxy Folder")); } else if (currentItem->itemType() == AbstractProjectItem::SubClipItem) { showClipProperties(std::static_pointer_cast(currentItem->parent()), false); m_openAction->setEnabled(false); m_reloadAction->setEnabled(false); m_locateAction->setEnabled(false); m_duplicateAction->setEnabled(false); m_deleteAction->setText(i18n("Delete Clip")); m_proxyAction->setText(i18n("Proxy Clip")); } m_deleteAction->setEnabled(true); } else { emit findInTimeline(QString()); m_reloadAction->setEnabled(false); m_locateAction->setEnabled(false); m_duplicateAction->setEnabled(false); m_openAction->setEnabled(false); m_deleteAction->setEnabled(false); } } else { // No item selected in bin m_openAction->setEnabled(false); m_deleteAction->setEnabled(false); showClipProperties(nullptr); emit findInTimeline(QString()); emit requestClipShow(nullptr); // clear effect stack emit requestShowEffectStack(QString(), nullptr, QSize(), false); // Display black bg in clip monitor emit openClip(std::shared_ptr()); } } std::vector Bin::selectedClipsIds(bool excludeFolders) { const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); std::vector ids; // We define the lambda that will be executed on each item of the subset of nodes of the tree that are selected auto itemAdder = [excludeFolders, &ids](std::vector &ids_vec, std::shared_ptr item) { auto binItem = std::static_pointer_cast(item); if (!excludeFolders || (binItem->itemType() != AbstractProjectItem::FolderItem && binItem->itemType() != AbstractProjectItem::FolderUpItem)) { ids.push_back(binItem->clipId()); } return ids_vec; }; for (const QModelIndex &ix : indexes) { if (!ix.isValid() || ix.column() != 0) { continue; } std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); item->accumulate(ids, itemAdder); } return ids; } QList> Bin::selectedClips() { auto ids = selectedClipsIds(true); QList> ret; for (const auto &id : ids) { ret.push_back(m_itemModel->getClipByBinID(id)); } return ret; } void Bin::slotInitView(QAction *action) { if (action) { m_proxyModel->selectionModel()->clearSelection(); int viewType = action->data().toInt(); KdenliveSettings::setBinMode(viewType); if (viewType == m_listType) { return; } if (m_listType == BinTreeView) { // save current treeview state (column width) QTreeView *view = static_cast(m_itemView); m_headerInfo = view->header()->saveState(); m_showDate->setEnabled(true); m_showDesc->setEnabled(true); } else { // remove the current folderUp item if any if (m_folderUp) { if (m_folderUp->parent()) { m_folderUp->parent()->removeChild(m_folderUp); } m_folderUp.reset(); } } m_listType = static_cast(viewType); } if (m_itemView) { delete m_itemView; } switch (m_listType) { case BinIconView: m_itemView = new MyListView(this); m_folderUp = ProjectFolderUp::construct(m_itemModel); m_showDate->setEnabled(false); m_showDesc->setEnabled(false); break; default: m_itemView = new MyTreeView(this); m_showDate->setEnabled(true); m_showDesc->setEnabled(true); break; } m_itemView->setMouseTracking(true); m_itemView->viewport()->installEventFilter(this); QSize zoom = m_iconSize * (m_slider->value() / 4.0); m_itemView->setIconSize(zoom); QPixmap pix(zoom); pix.fill(Qt::lightGray); m_blankThumb.addPixmap(pix); m_binTreeViewDelegate->setDar(pCore->getCurrentDar()); m_itemView->setModel(m_proxyModel); m_itemView->setSelectionModel(m_proxyModel->selectionModel()); m_layout->insertWidget(1, m_itemView); // setup some default view specific parameters if (m_listType == BinTreeView) { m_itemView->setItemDelegate(m_binTreeViewDelegate); MyTreeView *view = static_cast(m_itemView); view->setSortingEnabled(true); view->setWordWrap(true); connect(m_proxyModel, &QAbstractItemModel::layoutAboutToBeChanged, this, &Bin::slotSetSorting); connect(view, &MyTreeView::updateDragMode, m_itemModel.get(), &ProjectItemModel::setDragType, Qt::DirectConnection); m_proxyModel->setDynamicSortFilter(true); if (!m_headerInfo.isEmpty()) { view->header()->restoreState(m_headerInfo); } else { view->header()->resizeSections(QHeaderView::ResizeToContents); view->resizeColumnToContents(0); view->setColumnHidden(1, true); view->setColumnHidden(2, true); } m_showDate->setChecked(!view->isColumnHidden(1)); m_showDesc->setChecked(!view->isColumnHidden(2)); connect(view->header(), &QHeaderView::sectionResized, this, &Bin::slotSaveHeaders); connect(view->header(), &QHeaderView::sectionClicked, this, &Bin::slotSaveHeaders); connect(view, &MyTreeView::focusView, this, &Bin::slotGotFocus); } else if (m_listType == BinIconView) { MyListView *view = static_cast(m_itemView); connect(view, &MyListView::focusView, this, &Bin::slotGotFocus); } m_itemView->setEditTriggers(QAbstractItemView::NoEditTriggers); // DoubleClicked); m_itemView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_itemView->setDragDropMode(QAbstractItemView::DragDrop); m_itemView->setAlternatingRowColors(true); m_itemView->setAcceptDrops(true); m_itemView->setFocus(); } void Bin::slotSetIconSize(int size) { if (!m_itemView) { return; } KdenliveSettings::setBin_zoom(size); QSize zoom = m_iconSize; zoom = zoom * (size / 4.0); m_itemView->setIconSize(zoom); QPixmap pix(zoom); pix.fill(Qt::lightGray); m_blankThumb.addPixmap(pix); } void Bin::rebuildMenu() { m_transcodeAction = static_cast(pCore->window()->factory()->container(QStringLiteral("transcoders"), pCore->window())); m_extractAudioAction = static_cast(pCore->window()->factory()->container(QStringLiteral("extract_audio"), pCore->window())); m_clipsActionsMenu = static_cast(pCore->window()->factory()->container(QStringLiteral("clip_actions"), pCore->window())); m_menu->insertMenu(m_reloadAction, m_extractAudioAction); m_menu->insertMenu(m_reloadAction, m_transcodeAction); m_menu->insertMenu(m_reloadAction, m_clipsActionsMenu); m_inTimelineAction = m_menu->insertMenu(m_reloadAction, static_cast(pCore->window()->factory()->container(QStringLiteral("clip_in_timeline"), pCore->window()))); } void Bin::contextMenuEvent(QContextMenuEvent *event) { bool enableClipActions = false; ClipType::ProducerType type = ClipType::Unknown; bool isFolder = false; bool isImported = false; QString clipService; QString audioCodec; if (m_itemView) { QModelIndex idx = m_itemView->indexAt(m_itemView->viewport()->mapFromGlobal(event->globalPos())); if (idx.isValid()) { // User right clicked on a clip std::shared_ptr currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(idx)); if (currentItem) { enableClipActions = true; if (currentItem->itemType() == AbstractProjectItem::FolderItem) { isFolder = true; } else { auto clip = std::static_pointer_cast(currentItem); if (clip) { m_proxyAction->blockSignals(true); emit findInTimeline(clip->clipId(), clip->timelineInstances()); clipService = clip->getProducerProperty(QStringLiteral("mlt_service")); m_proxyAction->setChecked(clip->hasProxy()); QList transcodeActions; if (m_transcodeAction) { transcodeActions = m_transcodeAction->actions(); } QStringList dataList; QString condition; audioCodec = clip->codec(true); QString videoCodec = clip->codec(false); type = clip->clipType(); if (clip->hasUrl()) { isImported = true; } bool noCodecInfo = false; if (audioCodec.isEmpty() && videoCodec.isEmpty()) { noCodecInfo = true; } for (int i = 0; i < transcodeActions.count(); ++i) { dataList = transcodeActions.at(i)->data().toStringList(); if (dataList.count() > 4) { condition = dataList.at(4); if (condition.isEmpty()) { transcodeActions.at(i)->setEnabled(true); continue; } if (noCodecInfo) { // No audio / video codec, this is an MLT clip, disable conditionnal transcoding transcodeActions.at(i)->setEnabled(false); continue; } if (condition.startsWith(QLatin1String("vcodec"))) { transcodeActions.at(i)->setEnabled(condition.section(QLatin1Char('='), 1, 1) == videoCodec); } else if (condition.startsWith(QLatin1String("acodec"))) { transcodeActions.at(i)->setEnabled(condition.section(QLatin1Char('='), 1, 1) == audioCodec); } } } } m_proxyAction->blockSignals(false); } } } } // Enable / disable clip actions m_proxyAction->setEnabled((m_doc->getDocumentProperty(QStringLiteral("enableproxy")).toInt() != 0) && enableClipActions); m_openAction->setEnabled(type == ClipType::Image || type == ClipType::Audio || type == ClipType::TextTemplate || type == ClipType::Text); m_reloadAction->setEnabled(enableClipActions); m_locateAction->setEnabled(enableClipActions); m_duplicateAction->setEnabled(enableClipActions); m_editAction->setVisible(!isFolder); m_clipsActionsMenu->setEnabled(enableClipActions); m_extractAudioAction->setEnabled(enableClipActions); m_openAction->setVisible(!isFolder); m_reloadAction->setVisible(!isFolder); m_duplicateAction->setVisible(!isFolder); m_inTimelineAction->setVisible(!isFolder); if (m_transcodeAction) { m_transcodeAction->setEnabled(enableClipActions); m_transcodeAction->menuAction()->setVisible(!isFolder && clipService.contains(QStringLiteral("avformat"))); } m_clipsActionsMenu->menuAction()->setVisible( !isFolder && (clipService.contains(QStringLiteral("avformat")) || clipService.contains(QStringLiteral("xml")) || clipService.contains(QStringLiteral("consumer")))); m_extractAudioAction->menuAction()->setVisible(!isFolder && !audioCodec.isEmpty()); m_locateAction->setVisible(!isFolder && (isImported)); // Show menu event->setAccepted(true); if (enableClipActions) { m_menu->exec(event->globalPos()); } else { // Clicked in empty area m_addButton->menu()->exec(event->globalPos()); } } void Bin::slotItemDoubleClicked(const QModelIndex &ix, const QPoint pos) { std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); if (m_listType == BinIconView) { if (item->childCount() > 0 || item->itemType() == AbstractProjectItem::FolderItem) { m_folderUp->changeParent(std::static_pointer_cast(item)); m_itemView->setRootIndex(ix); return; } if (item == m_folderUp) { std::shared_ptr parentItem = item->parent(); QModelIndex parent = getIndexForId(parentItem->parent()->clipId(), parentItem->parent()->itemType() == AbstractProjectItem::FolderItem); if (parentItem->parent() != m_itemModel->getRootFolder()) { // We are entering a parent folder m_folderUp->changeParent(std::static_pointer_cast(parentItem->parent())); } else { m_folderUp->changeParent(std::shared_ptr()); } m_itemView->setRootIndex(m_proxyModel->mapFromSource(parent)); return; } } else { if (ix.column() == 0 && item->childCount() > 0) { QRect IconRect = m_itemView->visualRect(ix); IconRect.setWidth((double)IconRect.height() / m_itemView->iconSize().height() * m_itemView->iconSize().width()); if (!pos.isNull() && (IconRect.contains(pos) || pos.y() > (IconRect.y() + IconRect.height()/2))) { QTreeView *view = static_cast(m_itemView); view->setExpanded(ix, !view->isExpanded(ix)); return; } } } if (ix.isValid()) { QRect IconRect = m_itemView->visualRect(ix); IconRect.setWidth((double)IconRect.height() / m_itemView->iconSize().height() * m_itemView->iconSize().width()); if (!pos.isNull() && ((ix.column() == 2 && item->itemType() == AbstractProjectItem::ClipItem) || (!IconRect.contains(pos) && pos.y() < (IconRect.y() + IconRect.height()/2)))) { // User clicked outside icon, trigger rename m_itemView->edit(ix); return; } if (item->itemType() == AbstractProjectItem::ClipItem) { std::shared_ptr clip = std::static_pointer_cast(item); if (clip) { if (clip->clipType() == ClipType::Text || clip->clipType() == ClipType::TextTemplate) { // m_propertiesPanel->setEnabled(false); showTitleWidget(clip); } else { slotSwitchClipProperties(clip); } } } } } void Bin::slotEditClip() { QString panelId = m_propertiesPanel->property("clipId").toString(); QModelIndex current = m_proxyModel->selectionModel()->currentIndex(); std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(current)); if (item->clipId() != panelId) { // wrong clip return; } auto clip = std::static_pointer_cast(item); QString parentFolder = getCurrentFolder(); switch (clip->clipType()) { case ClipType::Text: case ClipType::TextTemplate: showTitleWidget(clip); break; case ClipType::SlideShow: showSlideshowWidget(clip); break; case ClipType::QText: ClipCreationDialog::createQTextClip(m_doc, parentFolder, this, clip.get()); break; default: break; } } void Bin::slotSwitchClipProperties() { QModelIndex current = m_proxyModel->selectionModel()->currentIndex(); if (current.isValid()) { // User clicked in the icon, open clip properties std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(current)); auto clip = std::static_pointer_cast(item); if (clip) { slotSwitchClipProperties(clip); return; } } slotSwitchClipProperties(nullptr); } void Bin::slotSwitchClipProperties(std::shared_ptr clip) { if (clip == nullptr) { m_propertiesPanel->setEnabled(false); return; } if (clip->clipType() == ClipType::SlideShow) { m_propertiesPanel->setEnabled(false); showSlideshowWidget(clip); } else if (clip->clipType() == ClipType::QText) { m_propertiesPanel->setEnabled(false); QString parentFolder = getCurrentFolder(); ClipCreationDialog::createQTextClip(m_doc, parentFolder, this, clip.get()); } else { m_propertiesPanel->setEnabled(true); showClipProperties(clip); m_propertiesDock->show(); m_propertiesDock->raise(); } // Check if properties panel is not tabbed under Bin // if (!pCore->window()->isTabbedWith(m_propertiesDock, QStringLiteral("project_bin"))) { } void Bin::doRefreshPanel(const QString &id) { std::shared_ptr currentItem = getFirstSelectedClip(); if ((currentItem != nullptr) && currentItem->AbstractProjectItem::clipId() == id) { showClipProperties(currentItem, true); } } void Bin::showClipProperties(std::shared_ptr clip, bool forceRefresh) { if ((clip == nullptr) || !clip->isReady()) { for (QWidget *w : m_propertiesPanel->findChildren()) { delete w; } m_propertiesPanel->setProperty("clipId", QString()); m_propertiesPanel->setEnabled(false); return; } m_propertiesPanel->setEnabled(true); QString panelId = m_propertiesPanel->property("clipId").toString(); if (!forceRefresh && panelId == clip->AbstractProjectItem::clipId()) { // the properties panel is already displaying current clip, do nothing return; } // Cleanup widget for new content for (QWidget *w : m_propertiesPanel->findChildren()) { delete w; } m_propertiesPanel->setProperty("clipId", clip->AbstractProjectItem::clipId()); QVBoxLayout *lay = static_cast(m_propertiesPanel->layout()); if (lay == nullptr) { lay = new QVBoxLayout(m_propertiesPanel); m_propertiesPanel->setLayout(lay); } ClipPropertiesController *panel = clip->buildProperties(m_propertiesPanel); connect(this, &Bin::refreshTimeCode, panel, &ClipPropertiesController::slotRefreshTimeCode); connect(panel, &ClipPropertiesController::updateClipProperties, this, &Bin::slotEditClipCommand); connect(panel, &ClipPropertiesController::seekToFrame, m_monitor, static_cast(&Monitor::slotSeek)); connect(panel, &ClipPropertiesController::editClip, this, &Bin::slotEditClip); connect(panel, SIGNAL(editAnalysis(QString, QString, QString)), this, SLOT(slotAddClipExtraData(QString, QString, QString))); lay->addWidget(panel); } void Bin::slotEditClipCommand(const QString &id, const QMap &oldProps, const QMap &newProps) { auto *command = new EditClipCommand(this, id, oldProps, newProps, true); m_doc->commandStack()->push(command); } void Bin::reloadClip(const QString &id) { std::shared_ptr clip = m_itemModel->getClipByBinID(id); if (!clip) { return; } clip->reloadProducer(); } void Bin::reloadMonitorIfActive(const QString &id) { if (m_monitor->activeClipId() == id) { slotOpenCurrent(); } } QStringList Bin::getBinFolderClipIds(const QString &id) const { QStringList ids; std::shared_ptr folder = m_itemModel->getFolderByBinId(id); if (folder) { for (int i = 0; i < folder->childCount(); i++) { std::shared_ptr child = std::static_pointer_cast(folder->child(i)); if (child->itemType() == AbstractProjectItem::ClipItem) { ids << child->clipId(); } } } return ids; } std::shared_ptr Bin::getBinClip(const QString &id) { std::shared_ptr clip = nullptr; if (id.contains(QLatin1Char('_'))) { clip = m_itemModel->getClipByBinID(id.section(QLatin1Char('_'), 0, 0)); } else if (!id.isEmpty()) { clip = m_itemModel->getClipByBinID(id); } return clip; } void Bin::setWaitingStatus(const QString &id) { std::shared_ptr clip = m_itemModel->getClipByBinID(id); if (clip) { clip->setClipStatus(AbstractProjectItem::StatusWaiting); } } void Bin::slotRemoveInvalidClip(const QString &id, bool replace, const QString &errorMessage) { Q_UNUSED(replace); std::shared_ptr clip = m_itemModel->getClipByBinID(id); if (!clip) { return; } emit requesteInvalidRemoval(id, clip->url(), errorMessage); } void Bin::selectClip(const std::shared_ptr &clip) { QModelIndex ix = m_itemModel->getIndexFromItem(clip); int row = ix.row(); const QModelIndex id = m_itemModel->index(row, 0, ix.parent()); const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, ix.parent()); if (id.isValid() && id2.isValid()) { m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), QItemSelectionModel::Select); } m_itemView->scrollTo(m_proxyModel->mapFromSource(ix)); } void Bin::slotOpenCurrent() { std::shared_ptr currentItem = getFirstSelectedClip(); if (currentItem) { emit openClip(currentItem); } } void Bin::openProducer(std::shared_ptr controller) { emit openClip(controller); } void Bin::openProducer(std::shared_ptr controller, int in, int out) { emit openClip(controller, in, out); } void Bin::emitItemUpdated(std::shared_ptr item) { emit itemUpdated(item); } void Bin::emitRefreshPanel(const QString &id) { emit refreshPanel(id); } void Bin::setupGeneratorMenu() { if (!m_menu) { qCDebug(KDENLIVE_LOG) << "Warning, menu was not created, something is wrong"; return; } QMenu *addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("generators"), pCore->window())); if (addMenu) { QMenu *menu = m_addButton->menu(); menu->addMenu(addMenu); addMenu->setEnabled(!addMenu->isEmpty()); m_addButton->setMenu(menu); } addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("extract_audio"), pCore->window())); if (addMenu) { m_menu->addMenu(addMenu); addMenu->setEnabled(!addMenu->isEmpty()); m_extractAudioAction = addMenu; } addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("transcoders"), pCore->window())); if (addMenu) { m_menu->addMenu(addMenu); addMenu->setEnabled(!addMenu->isEmpty()); m_transcodeAction = addMenu; } addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("clip_actions"), pCore->window())); if (addMenu) { m_menu->addMenu(addMenu); addMenu->setEnabled(!addMenu->isEmpty()); m_clipsActionsMenu = addMenu; } addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("clip_in_timeline"), pCore->window())); if (addMenu) { m_inTimelineAction = m_menu->addMenu(addMenu); m_inTimelineAction->setEnabled(!addMenu->isEmpty()); } if (m_locateAction) { m_menu->addAction(m_locateAction); } if (m_reloadAction) { m_menu->addAction(m_reloadAction); } if (m_duplicateAction) { m_menu->addAction(m_duplicateAction); } if (m_proxyAction) { m_menu->addAction(m_proxyAction); } addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("clip_timeline"), pCore->window())); if (addMenu) { m_menu->addMenu(addMenu); addMenu->setEnabled(false); } m_menu->addAction(m_editAction); m_menu->addAction(m_openAction); m_menu->addAction(m_renameAction); m_menu->addAction(m_deleteAction); m_menu->insertSeparator(m_deleteAction); } void Bin::setupMenu(QMenu *addMenu, QAction *defaultAction, const QHash &actions) { // Setup actions QAction *first = m_toolbar->actions().at(0); m_deleteAction = actions.value(QStringLiteral("delete")); m_toolbar->insertAction(first, m_deleteAction); QAction *folder = actions.value(QStringLiteral("folder")); m_toolbar->insertAction(m_deleteAction, folder); m_editAction = actions.value(QStringLiteral("properties")); m_openAction = actions.value(QStringLiteral("open")); m_reloadAction = actions.value(QStringLiteral("reload")); m_duplicateAction = actions.value(QStringLiteral("duplicate")); m_locateAction = actions.value(QStringLiteral("locate")); m_proxyAction = actions.value(QStringLiteral("proxy")); auto *m = new QMenu(this); m->addActions(addMenu->actions()); m_addButton = new QToolButton(this); m_addButton->setMenu(m); m_addButton->setDefaultAction(defaultAction); m_addButton->setPopupMode(QToolButton::MenuButtonPopup); m_toolbar->insertWidget(folder, m_addButton); m_menu = new QMenu(this); m_propertiesDock = pCore->window()->addDock(i18n("Clip Properties"), QStringLiteral("clip_properties"), m_propertiesPanel); m_propertiesDock->close(); // m_menu->addActions(addMenu->actions()); } const QString Bin::getDocumentProperty(const QString &key) { return m_doc->getDocumentProperty(key); } void Bin::slotUpdateJobStatus(const QString &id, int jobType, int status, const QString &label, const QString &actionName, const QString &details) { Q_UNUSED(id) Q_UNUSED(jobType) Q_UNUSED(status) Q_UNUSED(label) Q_UNUSED(actionName) Q_UNUSED(details) // TODO refac /* std::shared_ptr clip = m_itemModel->getClipByBinID(id); if (clip) { clip->setJobStatus((AbstractClipJob::JOBTYPE)jobType, (ClipJobStatus)status); } if (status == JobCrashed) { QList actions = m_infoMessage->actions(); if (m_infoMessage->isHidden()) { if (!details.isEmpty()) { m_infoMessage->setText(label + QStringLiteral(" ") + i18n("Show log") + QStringLiteral("")); } else { m_infoMessage->setText(label); } m_infoMessage->setWordWrap(m_infoMessage->text().length() > 35); m_infoMessage->setMessageType(KMessageWidget::Warning); } if (!actionName.isEmpty()) { QAction *action = nullptr; QList collections = KActionCollection::allCollections(); for (int i = 0; i < collections.count(); ++i) { KActionCollection *coll = collections.at(i); action = coll->action(actionName); if (action) { break; } } if ((action != nullptr) && !actions.contains(action)) { m_infoMessage->addAction(action); } } if (!details.isEmpty()) { m_errorLog.append(details); } m_infoMessage->setCloseButtonVisible(true); m_infoMessage->animatedShow(); } */ } void Bin::doDisplayMessage(const QString &text, KMessageWidget::MessageType type, const QList &actions) { // Remove existing actions if any QList acts = m_infoMessage->actions(); while (!acts.isEmpty()) { QAction *a = acts.takeFirst(); m_infoMessage->removeAction(a); delete a; } m_infoMessage->setText(text); m_infoMessage->setWordWrap(text.length() > 35); for (QAction *action : actions) { m_infoMessage->addAction(action); connect(action, &QAction::triggered, this, &Bin::slotMessageActionTriggered); } m_infoMessage->setCloseButtonVisible(actions.isEmpty()); m_infoMessage->setMessageType(type); m_infoMessage->animatedShow(); } void Bin::doDisplayMessage(const QString &text, KMessageWidget::MessageType type, const QString &logInfo) { // Remove existing actions if any QList acts = m_infoMessage->actions(); while (!acts.isEmpty()) { QAction *a = acts.takeFirst(); m_infoMessage->removeAction(a); delete a; } m_infoMessage->setText(text); m_infoMessage->setWordWrap(text.length() > 35); QAction *ac = new QAction(i18n("Show log"), this); m_infoMessage->addAction(ac); connect(ac, &QAction::triggered, [this, logInfo](bool) { KMessageBox::sorry(this, logInfo, i18n("Detailed log")); slotMessageActionTriggered(); }); m_infoMessage->setCloseButtonVisible(false); m_infoMessage->setMessageType(type); m_infoMessage->animatedShow(); } void Bin::refreshClip(const QString &id) { if (m_monitor->activeClipId() == id) { m_monitor->refreshMonitorIfActive(); } } void Bin::doRefreshAudioThumbs(const QString &id) { if (m_monitor->activeClipId() == id) { slotSendAudioThumb(id); } } void Bin::slotCreateProjectClip() { QAction *act = qobject_cast(sender()); if (act == nullptr) { // Cannot access triggering action, something is wrong qCDebug(KDENLIVE_LOG) << "// Error in clip creation action"; return; } ClipType::ProducerType type = (ClipType::ProducerType)act->data().toInt(); QStringList folderInfo = getFolderInfo(); QString parentFolder = getCurrentFolder(); switch (type) { case ClipType::Color: ClipCreationDialog::createColorClip(m_doc, parentFolder, m_itemModel); break; case ClipType::SlideShow: ClipCreationDialog::createSlideshowClip(m_doc, parentFolder, m_itemModel); break; case ClipType::Text: ClipCreationDialog::createTitleClip(m_doc, parentFolder, QString(), m_itemModel); break; case ClipType::TextTemplate: ClipCreationDialog::createTitleTemplateClip(m_doc, parentFolder, m_itemModel); break; case ClipType::QText: ClipCreationDialog::createQTextClip(m_doc, parentFolder, this); break; default: break; } } void Bin::slotItemDropped(const QStringList &ids, const QModelIndex &parent) { std::shared_ptr parentItem; if (parent.isValid()) { parentItem = m_itemModel->getBinItemByIndex(parent); parentItem = parentItem->getEnclosingFolder(false); } else { parentItem = m_itemModel->getRootFolder(); } auto *moveCommand = new QUndoCommand(); moveCommand->setText(i18np("Move Clip", "Move Clips", ids.count())); QStringList folderIds; for (const QString &id : ids) { if (id.contains(QLatin1Char('/'))) { // trying to move clip zone, not allowed. Ignore continue; } if (id.startsWith(QLatin1Char('#'))) { // moving a folder, keep it for later folderIds << id; continue; } std::shared_ptr currentItem = m_itemModel->getClipByBinID(id); if (!currentItem) { continue; } std::shared_ptr currentParent = currentItem->parent(); if (currentParent != parentItem) { // Item was dropped on a different folder new MoveBinClipCommand(this, id, currentParent->clipId(), parentItem->clipId(), moveCommand); } } if (!folderIds.isEmpty()) { for (QString id : folderIds) { id.remove(0, 1); std::shared_ptr currentItem = m_itemModel->getFolderByBinId(id); if (!currentItem) { continue; } std::shared_ptr currentParent = currentItem->parent(); if (currentParent != parentItem) { // Item was dropped on a different folder new MoveBinFolderCommand(this, id, currentParent->clipId(), parentItem->clipId(), moveCommand); } } } if (moveCommand->childCount() == 0) { pCore->displayMessage(i18n("No valid clip to insert"), InformationMessage, 500); } else { m_doc->commandStack()->push(moveCommand); } } void Bin::slotAddEffect(QString id, const QStringList &effectData) { if (id.isEmpty()) { id = m_monitor->activeClipId(); } if (!id.isEmpty()) { std::shared_ptr clip = m_itemModel->getClipByBinID(id); if (clip) { if (effectData.count() == 4) { // Paste effect from another stack std::shared_ptr sourceStack = pCore->getItemEffectStack(effectData.at(1).toInt(), effectData.at(2).toInt()); clip->copyEffect(sourceStack, effectData.at(3).toInt()); } else { clip->addEffect(effectData.constFirst()); } return; } } pCore->displayMessage(i18n("Select a clip to apply an effect"), InformationMessage, 500); } void Bin::slotEffectDropped(const QStringList &effectData, const QModelIndex &parent) { if (parent.isValid()) { std::shared_ptr parentItem = m_itemModel->getBinItemByIndex(parent); if (parentItem->itemType() != AbstractProjectItem::ClipItem) { // effect only supported on clip items return; } m_proxyModel->selectionModel()->clearSelection(); int row = parent.row(); const QModelIndex id = m_itemModel->index(row, 0, parent.parent()); const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, parent.parent()); if (id.isValid() && id2.isValid()) { m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), QItemSelectionModel::Select); } setCurrent(parentItem); if (effectData.count() == 4) { // Paste effect from another stack std::shared_ptr sourceStack = pCore->getItemEffectStack(effectData.at(1).toInt(), effectData.at(2).toInt()); std::static_pointer_cast(parentItem)->copyEffect(sourceStack, effectData.at(3).toInt()); } else { std::static_pointer_cast(parentItem)->addEffect(effectData.constFirst()); } } } void Bin::editMasterEffect(std::shared_ptr clip) { if (m_gainedFocus) { // Widget just gained focus, updating stack is managed in the eventfilter event, not from item return; } if (clip) { if (clip->itemType() == AbstractProjectItem::ClipItem) { std::shared_ptr clp = std::static_pointer_cast(clip); emit requestShowEffectStack(clp->clipName(), clp->m_effectStack, clp->getFrameSize(), false); return; } if (clip->itemType() == AbstractProjectItem::SubClipItem) { if (auto ptr = clip->parentItem().lock()) { std::shared_ptr clp = std::static_pointer_cast(ptr); emit requestShowEffectStack(clp->clipName(), clp->m_effectStack, clp->getFrameSize(), false); } return; } } emit requestShowEffectStack(QString(), nullptr, QSize(), false); } void Bin::slotGotFocus() { m_gainedFocus = true; } void Bin::doMoveClip(const QString &id, const QString &newParentId) { std::shared_ptr currentItem = m_itemModel->getClipByBinID(id); if (!currentItem) { return; } std::shared_ptr currentParent = currentItem->parent(); std::shared_ptr newParent = m_itemModel->getFolderByBinId(newParentId); currentItem->changeParent(newParent); } void Bin::doMoveFolder(const QString &id, const QString &newParentId) { std::shared_ptr currentItem = m_itemModel->getFolderByBinId(id); std::shared_ptr currentParent = currentItem->parent(); std::shared_ptr newParent = m_itemModel->getFolderByBinId(newParentId); currentParent->removeChild(currentItem); currentItem->changeParent(newParent); } void Bin::droppedUrls(const QList &urls, const QStringList &folderInfo) { QModelIndex current; if (folderInfo.isEmpty()) { current = m_proxyModel->mapToSource(m_proxyModel->selectionModel()->currentIndex()); } else { // get index for folder current = getIndexForId(folderInfo.constFirst(), true); } slotItemDropped(urls, current); } void Bin::slotAddClipToProject(const QUrl &url) { QList urls; urls << url; QModelIndex current = m_proxyModel->mapToSource(m_proxyModel->selectionModel()->currentIndex()); slotItemDropped(urls, current); } void Bin::slotItemDropped(const QList &urls, const QModelIndex &parent) { QString parentFolder = m_itemModel->getRootFolder()->clipId(); if (parent.isValid()) { // Check if drop occured on a folder std::shared_ptr parentItem = m_itemModel->getBinItemByIndex(parent); while (parentItem->itemType() != AbstractProjectItem::FolderItem) { parentItem = parentItem->parent(); } parentFolder = parentItem->clipId(); } ClipCreator::createClipsFromList(urls, true, parentFolder, m_itemModel); } void Bin::slotExpandUrl(const ItemInfo &info, const QString &url, QUndoCommand *command) { Q_UNUSED(info) Q_UNUSED(url) Q_UNUSED(command) // TODO reimplement this /* // Create folder to hold imported clips QString folderName = QFileInfo(url).fileName().section(QLatin1Char('.'), 0, 0); QString folderId = QString::number(getFreeFolderId()); new AddBinFolderCommand(this, folderId, folderName.isEmpty() ? i18n("Folder") : folderName, m_itemModel->getRootFolder()->clipId(), false, command); // Parse playlist clips QDomDocument doc; QFile file(url); doc.setContent(&file, false); file.close(); bool invalid = false; if (doc.documentElement().isNull()) { invalid = true; } QDomNodeList producers = doc.documentElement().elementsByTagName(QStringLiteral("producer")); QDomNodeList tracks = doc.documentElement().elementsByTagName(QStringLiteral("track")); if (invalid || producers.isEmpty()) { doDisplayMessage(i18n("Playlist clip %1 is invalid.", QFileInfo(url).fileName()), KMessageWidget::Warning); delete command; return; } if (tracks.count() > pCore->projectManager()->currentTimeline()->visibleTracksCount() + 1) { doDisplayMessage( i18n("Playlist clip %1 has too many tracks (%2) to be imported. Add new tracks to your project.", QFileInfo(url).fileName(), tracks.count()), KMessageWidget::Warning); delete command; return; } // Maps playlist producer IDs to (project) bin producer IDs. QMap idMap; // Maps hash IDs to (project) first playlist producer instance ID. This is // necessary to detect duplicate producer serializations produced by MLT. // This covers, for instance, images and titles. QMap hashToIdMap; QDir mltRoot(doc.documentElement().attribute(QStringLiteral("root"))); for (int i = 0; i < producers.count(); i++) { QDomElement prod = producers.at(i).toElement(); QString originalId = prod.attribute(QStringLiteral("id")); // track producer if (originalId.contains(QLatin1Char('_'))) { originalId = originalId.section(QLatin1Char('_'), 0, 0); } // slowmotion producer if (originalId.contains(QLatin1Char(':'))) { originalId = originalId.section(QLatin1Char(':'), 1, 1); } // We already have seen and mapped this producer. if (idMap.contains(originalId)) { continue; } // Check for duplicate producers, based on hash value of producer. // Be careful as to the kdenlive:file_hash! It is not unique for // title clips, nor color clips. Also not sure about image sequences. // So we use mlt service-specific hashes to identify duplicate producers. QString hash; QString mltService = Xml::getXmlProperty(prod, QStringLiteral("mlt_service")); if (mltService == QLatin1String("pixbuf") || mltService == QLatin1String("qimage") || mltService == QLatin1String("kdenlivetitle") || mltService == QLatin1String("color") || mltService == QLatin1String("colour")) { hash = mltService + QLatin1Char(':') + Xml::getXmlProperty(prod, QStringLiteral("kdenlive:clipname")) + QLatin1Char(':') + Xml::getXmlProperty(prod, QStringLiteral("kdenlive:folderid")) + QLatin1Char(':'); if (mltService == QLatin1String("kdenlivetitle")) { // Calculate hash based on title contents. hash.append( QString(QCryptographicHash::hash(Xml::getXmlProperty(prod, QStringLiteral("xmldata")).toUtf8(), QCryptographicHash::Md5).toHex())); } else if (mltService == QLatin1String("pixbuf") || mltService == QLatin1String("qimage") || mltService == QLatin1String("color") || mltService == QLatin1String("colour")) { hash.append(Xml::getXmlProperty(prod, QStringLiteral("resource"))); } QString singletonId = hashToIdMap.value(hash, QString()); if (singletonId.length() != 0) { // map duplicate producer ID to single bin clip producer ID. qCDebug(KDENLIVE_LOG) << "found duplicate producer:" << hash << ", reusing newID:" << singletonId; idMap.insert(originalId, singletonId); continue; } } // First occurrence of a producer, so allocate new bin clip producer ID. QString newId = QString::number(getFreeClipId()); idMap.insert(originalId, newId); qCDebug(KDENLIVE_LOG) << "originalId: " << originalId << ", newId: " << newId; // Ensure to register new bin clip producer ID in hash hashmap for // those clips that MLT likes to serialize multiple times. This is // indicated by having a hash "value" unqual "". See also above. if (hash.length() != 0) { hashToIdMap.insert(hash, newId); } // Add clip QDomElement clone = prod.cloneNode(true).toElement(); EffectsList::setProperty(clone, QStringLiteral("kdenlive:folderid"), folderId); // Do we have a producer that uses a resource property that contains a path? if (mltService == QLatin1String("avformat-novalidate") // av clip || mltService == QLatin1String("avformat") // av clip || mltService == QLatin1String("pixbuf") // image (sequence) clip || mltService == QLatin1String("qimage") // image (sequence) clip || mltService == QLatin1String("xml") // MLT playlist clip, someone likes recursion :) ) { // Make sure to correctly resolve relative resource paths based on // the playlist's root, not on this project's root QString resource = Xml::getXmlProperty(clone, QStringLiteral("resource")); if (QFileInfo(resource).isRelative()) { QFileInfo rootedResource(mltRoot, resource); qCDebug(KDENLIVE_LOG) << "fixed resource path for producer, newId:" << newId << "resource:" << rootedResource.absoluteFilePath(); EffectsList::setProperty(clone, QStringLiteral("resource"), rootedResource.absoluteFilePath()); } } ClipCreationDialog::createClipsCommand(this, clone, newId, command); } pCore->projectManager()->currentTimeline()->importPlaylist(info, idMap, doc, command); */ } void Bin::slotItemEdited(const QModelIndex &ix, const QModelIndex &, const QVector &roles) { if (ix.isValid() && roles.contains(AbstractProjectItem::DataName)) { // Clip renamed std::shared_ptr item = m_itemModel->getBinItemByIndex(ix); auto clip = std::static_pointer_cast(item); if (clip) { emit clipNameChanged(clip->AbstractProjectItem::clipId()); } } } void Bin::renameSubClipCommand(const QString &id, const QString &newName, const QString &oldName, int in, int out) { auto *command = new RenameBinSubClipCommand(this, id, newName, oldName, in, out); m_doc->commandStack()->push(command); } void Bin::renameSubClip(const QString &id, const QString &newName, const QString &oldName, int in, int out) { std::shared_ptr clip = m_itemModel->getClipByBinID(id); if (!clip) { return; } std::shared_ptr sub = clip->getSubClip(in, out); if (!sub) { return; } sub->setName(newName); clip->setProducerProperty("kdenlive:clipzone." + oldName, QString()); clip->setProducerProperty("kdenlive:clipzone." + newName, QString::number(in) + QLatin1Char(';') + QString::number(out)); emit itemUpdated(sub); } Timecode Bin::projectTimecode() const { return m_doc->timecode(); } void Bin::slotStartFilterJob(const ItemInfo &info, const QString &id, QMap &filterParams, QMap &consumerParams, QMap &extraParams) { Q_UNUSED(info) Q_UNUSED(id) Q_UNUSED(filterParams) Q_UNUSED(consumerParams) Q_UNUSED(extraParams) // TODO refac /* std::shared_ptr clip = getBinClip(id); if (!clip) { return; } QMap producerParams = QMap(); producerParams.insert(QStringLiteral("producer"), clip->url()); if (info.cropDuration != GenTime()) { producerParams.insert(QStringLiteral("in"), QString::number((int)info.cropStart.frames(pCore->getCurrentFps()))); producerParams.insert(QStringLiteral("out"), QString::number((int)(info.cropStart + info.cropDuration).frames(pCore->getCurrentFps()))); extraParams.insert(QStringLiteral("clipStartPos"), QString::number((int)info.startPos.frames(pCore->getCurrentFps()))); extraParams.insert(QStringLiteral("clipTrack"), QString::number(info.track)); } else { // We want to process whole clip producerParams.insert(QStringLiteral("in"), QString::number(0)); producerParams.insert(QStringLiteral("out"), QString::number(-1)); } */ } void Bin::focusBinView() const { m_itemView->setFocus(); } void Bin::slotOpenClip() { std::shared_ptr clip = getFirstSelectedClip(); if (!clip) { return; } switch (clip->clipType()) { case ClipType::Text: case ClipType::TextTemplate: showTitleWidget(clip); break; case ClipType::Image: if (KdenliveSettings::defaultimageapp().isEmpty()) { KMessageBox::sorry(QApplication::activeWindow(), i18n("Please set a default application to open images in the Settings dialog")); } else { QProcess::startDetached(KdenliveSettings::defaultimageapp(), QStringList() << clip->url()); } break; case ClipType::Audio: if (KdenliveSettings::defaultaudioapp().isEmpty()) { KMessageBox::sorry(QApplication::activeWindow(), i18n("Please set a default application to open audio files in the Settings dialog")); } else { QProcess::startDetached(KdenliveSettings::defaultaudioapp(), QStringList() << clip->url()); } break; default: break; } } void Bin::updateTimecodeFormat() { emit refreshTimeCode(); } /* void Bin::slotGotFilterJobResults(const QString &id, int startPos, int track, const stringMap &results, const stringMap &filterInfo) { if (filterInfo.contains(QStringLiteral("finalfilter"))) { if (filterInfo.contains(QStringLiteral("storedata"))) { // Store returned data as clip extra data std::shared_ptr clip = getBinClip(id); if (clip) { QString key = filterInfo.value(QStringLiteral("key")); QStringList newValue = clip->updatedAnalysisData(key, results.value(key), filterInfo.value(QStringLiteral("offset")).toInt()); slotAddClipExtraData(id, newValue.at(0), newValue.at(1)); } } if (startPos == -1) { // Processing bin clip std::shared_ptr currentItem = m_itemModel->getClipByBinID(id); if (!currentItem) { return; } std::shared_ptr ctl = std::static_pointer_cast(currentItem); EffectsList list = ctl->effectList(); QDomElement effect = list.effectById(filterInfo.value(QStringLiteral("finalfilter"))); QDomDocument doc; QDomElement e = doc.createElement(QStringLiteral("test")); doc.appendChild(e); e.appendChild(doc.importNode(effect, true)); if (!effect.isNull()) { QDomElement newEffect = effect.cloneNode().toElement(); QMap::const_iterator i = results.constBegin(); while (i != results.constEnd()) { EffectsList::setParameter(newEffect, i.key(), i.value()); ++i; } ctl->updateEffect(newEffect, effect.attribute(QStringLiteral("kdenlive_ix")).toInt()); emit requestClipShow(currentItem); // TODO use undo / redo for bin clip edit effect // EditEffectCommand *command = new EditEffectCommand(this, clip->track(), clip->startPos(), effect, newEffect, clip->selectedEffectIndex(), true, true); m_commandStack->push(command); emit clipItemSelected(clip); } // emit gotFilterJobResults(id, startPos, track, results, filterInfo); return; } // This is a timeline filter, forward results emit gotFilterJobResults(id, startPos, track, results, filterInfo); return; } // Currently, only the first value of results is used std::shared_ptr clip = getBinClip(id); if (!clip) { return; } // Check for return value int markersType = -1; if (filterInfo.contains(QStringLiteral("addmarkers"))) { markersType = filterInfo.value(QStringLiteral("addmarkers")).toInt(); } if (results.isEmpty()) { emit displayBinMessage(i18n("No data returned from clip analysis"), KMessageWidget::Warning); return; } bool dataProcessed = false; QString label = filterInfo.value(QStringLiteral("label")); QString key = filterInfo.value(QStringLiteral("key")); int offset = filterInfo.value(QStringLiteral("offset")).toInt(); QStringList value = results.value(key).split(QLatin1Char(';'), QString::SkipEmptyParts); // qCDebug(KDENLIVE_LOG)<<"// RESULT; "<setText(i18n("Auto Split Clip")); for (const QString &pos : value) { if (!pos.contains(QLatin1Char('='))) { continue; } int newPos = pos.section(QLatin1Char('='), 0, 0).toInt(); // Don't use scenes shorter than 1 second if (newPos - cutPos < 24) { continue; } new AddBinClipCutCommand(this, id, cutPos + offset, newPos + offset, true, command); cutPos = newPos; } if (command->childCount() == 0) { delete command; } else { m_doc->commandStack()->push(command); } } if (markersType >= 0) { // Add markers from returned data dataProcessed = true; int cutPos = 0; int index = 1; bool simpleList = false; double sourceFps = clip->getOriginalFps(); if (qFuzzyIsNull(sourceFps)) { sourceFps = pCore->getCurrentFps(); } if (filterInfo.contains(QStringLiteral("simplelist"))) { // simple list simpleList = true; } for (const QString &pos : value) { if (simpleList) { clip->getMarkerModel()->addMarker(GenTime((int)(pos.toInt() * pCore->getCurrentFps() / sourceFps), pCore->getCurrentFps()), label + pos, markersType); index++; continue; } if (!pos.contains(QLatin1Char('='))) { continue; } int newPos = pos.section(QLatin1Char('='), 0, 0).toInt(); // Don't use scenes shorter than 1 second if (newPos - cutPos < 24) { continue; } clip->getMarkerModel()->addMarker(GenTime(newPos + offset, pCore->getCurrentFps()), label + QString::number(index), markersType); index++; cutPos = newPos; } } if (!dataProcessed || filterInfo.contains(QStringLiteral("storedata"))) { // Store returned data as clip extra data QStringList newValue = clip->updatedAnalysisData(key, results.value(key), offset); slotAddClipExtraData(id, newValue.at(0), newValue.at(1)); } } */ void Bin::slotGetCurrentProjectImage(const QString &clipId, bool request) { Q_UNUSED(clipId) // TODO refact : look at this // if (!clipId.isEmpty()) { // (pCore->projectManager()->currentTimeline()->hideClip(clipId, request)); // } pCore->monitorManager()->projectMonitor()->slotGetCurrentImage(request); } // TODO: move title editing into a better place... void Bin::showTitleWidget(std::shared_ptr clip) { QString path = clip->getProducerProperty(QStringLiteral("resource")); QDir titleFolder(m_doc->projectDataFolder() + QStringLiteral("/titles")); titleFolder.mkpath(QStringLiteral(".")); TitleWidget dia_ui(QUrl(), m_doc->timecode(), titleFolder.absolutePath(), pCore->monitorManager()->projectMonitor(), pCore->window()); connect(&dia_ui, &TitleWidget::requestBackgroundFrame, this, &Bin::slotGetCurrentProjectImage); QDomDocument doc; QString xmldata = clip->getProducerProperty(QStringLiteral("xmldata")); if (xmldata.isEmpty() && QFile::exists(path)) { QFile file(path); doc.setContent(&file, false); file.close(); } else { doc.setContent(xmldata); } dia_ui.setXml(doc, clip->AbstractProjectItem::clipId()); if (dia_ui.exec() == QDialog::Accepted) { QMap newprops; newprops.insert(QStringLiteral("xmldata"), dia_ui.xml().toString()); if (dia_ui.duration() != clip->duration().frames(pCore->getCurrentFps()) + 1) { // duration changed, we need to update duration newprops.insert(QStringLiteral("out"), QString::number(dia_ui.duration() - 1)); int currentLength = clip->getProducerIntProperty(QStringLiteral("kdenlive:duration")); if (currentLength != dia_ui.duration()) { newprops.insert(QStringLiteral("kdenlive:duration"), QString::number(dia_ui.duration())); } } // trigger producer reload newprops.insert(QStringLiteral("force_reload"), QStringLiteral("2")); if (!path.isEmpty()) { // we are editing an external file, asked if we want to detach from that file or save the result to that title file. if (KMessageBox::questionYesNo(pCore->window(), i18n("You are editing an external title clip (%1). Do you want to save your changes to the title " "file or save the changes for this project only?", path), i18n("Save Title"), KGuiItem(i18n("Save to title file")), KGuiItem(i18n("Save in project only"))) == KMessageBox::Yes) { // save to external file dia_ui.saveTitle(QUrl::fromLocalFile(path)); } else { newprops.insert(QStringLiteral("resource"), QString()); } } slotEditClipCommand(clip->AbstractProjectItem::clipId(), clip->currentProperties(newprops), newprops); } } void Bin::slotResetInfoMessage() { m_errorLog.clear(); QList actions = m_infoMessage->actions(); for (int i = 0; i < actions.count(); ++i) { m_infoMessage->removeAction(actions.at(i)); } } void Bin::emitMessage(const QString &text, int progress, MessageType type) { emit displayMessage(text, progress, type); } void Bin::slotSetSorting() { QTreeView *view = qobject_cast(m_itemView); if (view) { int ix = view->header()->sortIndicatorSection(); m_proxyModel->setFilterKeyColumn(ix); } } void Bin::slotShowDateColumn(bool show) { QTreeView *view = qobject_cast(m_itemView); if (view) { view->setColumnHidden(1, !show); } } void Bin::slotShowDescColumn(bool show) { QTreeView *view = qobject_cast(m_itemView); if (view) { view->setColumnHidden(2, !show); } } void Bin::slotQueryRemoval(const QString &id, const QString &url, const QString &errorMessage) { if (m_invalidClipDialog) { if (!url.isEmpty()) { m_invalidClipDialog->addClip(id, url); } return; } QString message = i18n("Clip is invalid, will be removed from project."); if (!errorMessage.isEmpty()) { message.append("\n" + errorMessage); } m_invalidClipDialog = new InvalidDialog(i18n("Invalid clip"), message, true, this); m_invalidClipDialog->addClip(id, url); int result = m_invalidClipDialog->exec(); if (result == QDialog::Accepted) { const QStringList ids = m_invalidClipDialog->getIds(); Fun undo = []() { return true; }; Fun redo = []() { return true; }; for (const QString &i : ids) { auto item = m_itemModel->getClipByBinID(i); m_itemModel->requestBinClipDeletion(item, undo, redo); } } delete m_invalidClipDialog; m_invalidClipDialog = nullptr; } void Bin::slotRefreshClipThumbnail(const QString &id) { std::shared_ptr clip = m_itemModel->getClipByBinID(id); if (!clip) { return; } clip->reloadProducer(true); } void Bin::slotAddClipExtraData(const QString &id, const QString &key, const QString &clipData, QUndoCommand *groupCommand) { std::shared_ptr clip = m_itemModel->getClipByBinID(id); if (!clip) { return; } QString oldValue = clip->getProducerProperty(key); QMap oldProps; oldProps.insert(key, oldValue); QMap newProps; newProps.insert(key, clipData); auto *command = new EditClipCommand(this, id, oldProps, newProps, true, groupCommand); if (!groupCommand) { m_doc->commandStack()->push(command); } } void Bin::slotUpdateClipProperties(const QString &id, const QMap &properties, bool refreshPropertiesPanel) { std::shared_ptr clip = m_itemModel->getClipByBinID(id); if (clip) { clip->setProperties(properties, refreshPropertiesPanel); } } void Bin::updateTimelineProducers(const QString &id, const QMap &passProperties) { Q_UNUSED(id) Q_UNUSED(passProperties) // TODO REFAC // pCore->projectManager()->currentTimeline()->updateClipProperties(id, passProperties); // m_doc->renderer()->updateSlowMotionProducers(id, passProperties); } void Bin::showSlideshowWidget(std::shared_ptr clip) { QString folder = QFileInfo(clip->url()).absolutePath(); qCDebug(KDENLIVE_LOG) << " ** * CLIP ABS PATH: " << clip->url() << " = " << folder; SlideshowClip *dia = new SlideshowClip(m_doc->timecode(), folder, clip.get(), this); if (dia->exec() == QDialog::Accepted) { // edit clip properties QMap properties; properties.insert(QStringLiteral("out"), QString::number(m_doc->getFramePos(dia->clipDuration()) * dia->imageCount() - 1)); properties.insert(QStringLiteral("kdenlive:duration"), QString::number(m_doc->getFramePos(dia->clipDuration()) * dia->imageCount())); properties.insert(QStringLiteral("kdenlive:clipname"), dia->clipName()); properties.insert(QStringLiteral("ttl"), QString::number(m_doc->getFramePos(dia->clipDuration()))); properties.insert(QStringLiteral("loop"), QString::number(static_cast(dia->loop()))); properties.insert(QStringLiteral("crop"), QString::number(static_cast(dia->crop()))); properties.insert(QStringLiteral("fade"), QString::number(static_cast(dia->fade()))); properties.insert(QStringLiteral("luma_duration"), QString::number(m_doc->getFramePos(dia->lumaDuration()))); properties.insert(QStringLiteral("luma_file"), dia->lumaFile()); properties.insert(QStringLiteral("softness"), QString::number(dia->softness())); properties.insert(QStringLiteral("animation"), dia->animation()); QMap oldProperties; oldProperties.insert(QStringLiteral("out"), clip->getProducerProperty(QStringLiteral("out"))); oldProperties.insert(QStringLiteral("kdenlive:duration"), clip->getProducerProperty(QStringLiteral("kdenlive:duration"))); oldProperties.insert(QStringLiteral("kdenlive:clipname"), clip->name()); oldProperties.insert(QStringLiteral("ttl"), clip->getProducerProperty(QStringLiteral("ttl"))); oldProperties.insert(QStringLiteral("loop"), clip->getProducerProperty(QStringLiteral("loop"))); oldProperties.insert(QStringLiteral("crop"), clip->getProducerProperty(QStringLiteral("crop"))); oldProperties.insert(QStringLiteral("fade"), clip->getProducerProperty(QStringLiteral("fade"))); oldProperties.insert(QStringLiteral("luma_duration"), clip->getProducerProperty(QStringLiteral("luma_duration"))); oldProperties.insert(QStringLiteral("luma_file"), clip->getProducerProperty(QStringLiteral("luma_file"))); oldProperties.insert(QStringLiteral("softness"), clip->getProducerProperty(QStringLiteral("softness"))); oldProperties.insert(QStringLiteral("animation"), clip->getProducerProperty(QStringLiteral("animation"))); slotEditClipCommand(clip->AbstractProjectItem::clipId(), oldProperties, properties); } delete dia; } void Bin::setBinEffectsEnabled(bool enabled) { QAction *disableEffects = pCore->window()->actionCollection()->action(QStringLiteral("disable_bin_effects")); if (disableEffects) { if (enabled == disableEffects->isChecked()) { return; } disableEffects->blockSignals(true); disableEffects->setChecked(!enabled); disableEffects->blockSignals(false); } m_itemModel->setBinEffectsEnabled(enabled); pCore->projectManager()->disableBinEffects(!enabled); } void Bin::slotRenameItem() { const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedRows(0); for (const QModelIndex &ix : indexes) { if (!ix.isValid()) { continue; } m_itemView->setCurrentIndex(ix); m_itemView->edit(ix); return; } } void Bin::refreshProxySettings() { QList> clipList = m_itemModel->getRootFolder()->childClips(); auto *masterCommand = new QUndoCommand(); masterCommand->setText(m_doc->useProxy() ? i18n("Enable proxies") : i18n("Disable proxies")); // en/disable proxy option in clip properties for (QWidget *w : m_propertiesPanel->findChildren()) { static_cast(w)->enableProxy(m_doc->useProxy()); } if (!m_doc->useProxy()) { // Disable all proxies m_doc->slotProxyCurrentItem(false, clipList, false, masterCommand); } else { QList> toProxy; for (std::shared_ptr clp : clipList) { ClipType::ProducerType t = clp->clipType(); if (t == ClipType::Playlist) { toProxy << clp; continue; } else if ((t == ClipType::AV || t == ClipType::Video) && m_doc->autoGenerateProxy(clp->getProducerIntProperty(QStringLiteral("meta.media.width")))) { // Start proxy toProxy << clp; continue; } else if (t == ClipType::Image && m_doc->autoGenerateImageProxy(clp->getProducerIntProperty(QStringLiteral("meta.media.width")))) { // Start proxy toProxy << clp; continue; } } if (!toProxy.isEmpty()) { m_doc->slotProxyCurrentItem(true, toProxy, false, masterCommand); } } if (masterCommand->childCount() > 0) { m_doc->commandStack()->push(masterCommand); } else { delete masterCommand; } } QStringList Bin::getProxyHashList() { QStringList list; QList> clipList = m_itemModel->getRootFolder()->childClips(); for (std::shared_ptr clp : clipList) { if (clp->clipType() == ClipType::AV || clp->clipType() == ClipType::Video || clp->clipType() == ClipType::Playlist) { list << clp->hash(); } } return list; } void Bin::slotSendAudioThumb(const QString &id) { std::shared_ptr clip = m_itemModel->getClipByBinID(id); if ((clip != nullptr) && clip->audioThumbCreated()) { m_monitor->prepareAudioThumb(clip->audioChannels(), clip->audioFrameCache); } else { QVariantList list; m_monitor->prepareAudioThumb(0, list); } } bool Bin::isEmpty() const { // TODO: return true if we only have folders if (m_itemModel->getRootFolder() == nullptr) { return true; } return m_itemModel->getRootFolder()->childCount() == 0; } void Bin::reloadAllProducers() { if (m_itemModel->getRootFolder() == nullptr || m_itemModel->getRootFolder()->childCount() == 0 || !isEnabled()) { return; } QList> clipList = m_itemModel->getRootFolder()->childClips(); emit openClip(std::shared_ptr()); for (std::shared_ptr clip : clipList) { QDomDocument doc; QDomElement xml = clip->toXml(doc); // Make sure we reload clip length xml.removeAttribute(QStringLiteral("out")); Xml::removeXmlProperty(xml, QStringLiteral("length")); if (!xml.isNull()) { clip->setClipStatus(AbstractProjectItem::StatusWaiting); clip->discardAudioThumb(); pCore->jobManager()->slotDiscardClipJobs(clip->clipId()); // We need to set a temporary id before all outdated producers are replaced; int jobId = pCore->jobManager()->startJob({clip->clipId()}, -1, QString(), xml); pCore->jobManager()->startJob({clip->clipId()}, jobId, QString(), 150, -1, true, true); pCore->jobManager()->startJob({clip->clipId()}, jobId, QString()); } } } void Bin::slotMessageActionTriggered() { m_infoMessage->animatedHide(); } void Bin::resetUsageCount() { const QList> clipList = m_itemModel->getRootFolder()->childClips(); for (std::shared_ptr clip : clipList) { clip->setRefCount(0); } } void Bin::getBinStats(uint *used, uint *unused, qint64 *usedSize, qint64 *unusedSize) { QList> clipList = m_itemModel->getRootFolder()->childClips(); for (std::shared_ptr clip : clipList) { if (clip->refCount() == 0) { *unused += 1; *unusedSize += clip->getProducerInt64Property(QStringLiteral("kdenlive:file_size")); } else { *used += 1; *usedSize += clip->getProducerInt64Property(QStringLiteral("kdenlive:file_size")); } } } QDir Bin::getCacheDir(CacheType type, bool *ok) const { return m_doc->getCacheDir(type, ok); } void Bin::rebuildProxies() { QList> clipList = m_itemModel->getRootFolder()->childClips(); QList> toProxy; for (std::shared_ptr clp : clipList) { if (clp->hasProxy()) { toProxy << clp; // Abort all pending jobs pCore->jobManager()->discardJobs(clp->clipId(), AbstractClipJob::PROXYJOB); clp->deleteProxy(); } } if (toProxy.isEmpty()) { return; } auto *masterCommand = new QUndoCommand(); masterCommand->setText(i18n("Rebuild proxies")); m_doc->slotProxyCurrentItem(true, toProxy, true, masterCommand); if (masterCommand->childCount() > 0) { m_doc->commandStack()->push(masterCommand); } else { delete masterCommand; } } void Bin::showClearButton(bool show) { m_searchLine->setClearButtonEnabled(show); } void Bin::saveZone(const QStringList &info, const QDir &dir) { if (info.size() != 3) { return; } std::shared_ptr clip = getBinClip(info.constFirst()); if (clip) { QPoint zone(info.at(1).toInt(), info.at(2).toInt()); clip->saveZone(zone, dir); } } void Bin::setCurrent(std::shared_ptr item) { switch (item->itemType()) { case AbstractProjectItem::ClipItem: { openProducer(std::static_pointer_cast(item)); std::shared_ptr clp = std::static_pointer_cast(item); emit requestShowEffectStack(clp->clipName(), clp->m_effectStack, clp->getFrameSize(), false); break; } case AbstractProjectItem::SubClipItem: { auto subClip = std::static_pointer_cast(item); QPoint zone = subClip->zone(); openProducer(subClip->getMasterClip(), zone.x(), zone.y()); break; } case AbstractProjectItem::FolderUpItem: case AbstractProjectItem::FolderItem: default: break; } } void Bin::cleanup() { m_itemModel->requestCleanup(); } std::shared_ptr Bin::getClipEffectStack(int itemId) { std::shared_ptr clip = m_itemModel->getClipByBinID(QString::number(itemId)); Q_ASSERT(clip != nullptr); std::shared_ptr effectStack = std::static_pointer_cast(clip)->m_effectStack; return effectStack; } int Bin::getClipDuration(int itemId) const { std::shared_ptr clip = m_itemModel->getClipByBinID(QString::number(itemId)); Q_ASSERT(clip != nullptr); return clip->frameDuration(); } PlaylistState::ClipState Bin::getClipState(int itemId) const { std::shared_ptr clip = m_itemModel->getClipByBinID(QString::number(itemId)); Q_ASSERT(clip != nullptr); bool audio = clip->hasAudio(); bool video = clip->hasVideo(); return audio ? (video ? PlaylistState::Disabled : PlaylistState::AudioOnly) : PlaylistState::VideoOnly; } QString Bin::getCurrentFolder() { // Check parent item QModelIndex ix = m_proxyModel->selectionModel()->currentIndex(); std::shared_ptr parentFolder = m_itemModel->getRootFolder(); if (ix.isValid() && m_proxyModel->selectionModel()->isSelected(ix)) { std::shared_ptr currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix)); parentFolder = std::static_pointer_cast(currentItem->getEnclosingFolder()); } return parentFolder->clipId(); } + +void Bin::adjustProjectProfileToItem() +{ + QModelIndex current = m_proxyModel->selectionModel()->currentIndex(); + if (current.isValid()) { + // User clicked in the icon, open clip properties + std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(current)); + auto clip = std::static_pointer_cast(item); + if (clip) { + QDomDocument doc; + LoadJob::checkProfile(clip->clipId(), clip->toXml(doc, false), clip->originalProducer()); + } + } +} diff --git a/src/bin/bin.h b/src/bin/bin.h index 9cd701699..a25a0242e 100644 --- a/src/bin/bin.h +++ b/src/bin/bin.h @@ -1,468 +1,470 @@ /* Copyright (C) 2012 Till Theato Copyright (C) 2014 Jean-Baptiste Mardelle This file is part of Kdenlive. See www.kdenlive.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KDENLIVE_BIN_H #define KDENLIVE_BIN_H #include "abstractprojectitem.h" #include "timecode.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class AbstractProjectItem; class BinItemDelegate; class ClipController; class EffectStackModel; class InvalidDialog; class KdenliveDoc; class Monitor; class ProjectClip; class ProjectFolder; class ProjectFolderUp; class ProjectItemModel; class ProjectSortProxyModel; class QDockWidget; class QMenu; class QScrollArea; class QTimeLine; class QToolBar; class QToolButton; class QUndoCommand; class QVBoxLayout; class SmallJobLabel; namespace Mlt { class Producer; } class MyListView : public QListView { Q_OBJECT public: explicit MyListView(QWidget *parent = nullptr); protected: void focusInEvent(QFocusEvent *event) override; signals: void focusView(); void updateDragMode(ClipType::ProducerType type); }; class MyTreeView : public QTreeView { Q_OBJECT Q_PROPERTY(bool editing READ isEditing WRITE setEditing) public: explicit MyTreeView(QWidget *parent = nullptr); void setEditing(bool edit); protected: void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void focusInEvent(QFocusEvent *event) override; void keyPressEvent(QKeyEvent *event) override; protected slots: void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) override; void editorDestroyed(QObject *editor) override; private: QPoint m_startPos; PlaylistState::ClipState m_dragType; bool m_editing; bool performDrag(); bool isEditing() const; signals: void focusView(); void updateDragMode(PlaylistState::ClipState type); }; class SmallJobLabel : public QPushButton { Q_OBJECT public: explicit SmallJobLabel(QWidget *parent = nullptr); static const QString getStyleSheet(const QPalette &p); void setAction(QAction *action); private: enum ItemRole { NameRole = Qt::UserRole, DurationRole, UsageRole }; QTimeLine *m_timeLine; QAction *m_action; public slots: void slotSetJobCount(int jobCount); private slots: void slotTimeLineChanged(qreal value); void slotTimeLineFinished(); }; class LineEventEater : public QObject { Q_OBJECT public: explicit LineEventEater(QObject *parent = nullptr); protected: bool eventFilter(QObject *obj, QEvent *event) override; signals: void clearSearchLine(); void showClearButton(bool); }; /** * @class Bin * @brief The bin widget takes care of both item model and view upon project opening. */ class Bin : public QWidget { Q_OBJECT /** @brief Defines the view types (icon view, tree view,...) */ enum BinViewType { BinTreeView, BinIconView }; public: explicit Bin(const std::shared_ptr &model, QWidget *parent = nullptr); ~Bin(); bool isLoading; /** @brief Sets the document for the bin and initialize some stuff */ void setDocument(KdenliveDoc *project); /** @brief Create a clip item from its xml description */ void createClip(const QDomElement &xml); /** @brief Used to notify the Model View that an item was updated */ void emitItemUpdated(std::shared_ptr item); /** @brief Set monitor associated with this bin (clipmonitor) */ void setMonitor(Monitor *monitor); /** @brief Returns the clip monitor */ Monitor *monitor(); /** @brief Open a producer in the clip monitor */ void openProducer(std::shared_ptr controller); void openProducer(std::shared_ptr controller, int in, int out); /** @brief Get a clip from it's id */ std::shared_ptr getBinClip(const QString &id); /** @brief Returns a list of selected clip ids @param excludeFolders: if true, ids of folders are not returned */ std::vector selectedClipsIds(bool excludeFolders = true); // Returns the selected clips QList> selectedClips(); /** @brief Current producer has changed, refresh monitor and timeline*/ void refreshClip(const QString &id); void setupMenu(QMenu *addMenu, QAction *defaultAction, const QHash &actions); /** @brief The source file was modified, we will reload it soon, disable item in the meantime */ void setWaitingStatus(const QString &id); const QString getDocumentProperty(const QString &key); /** @brief Ask MLT to reload this clip's producer */ void reloadClip(const QString &id); /** @brief refresh monitor (if clip changed) */ void reloadMonitorIfActive(const QString &id); void doMoveClip(const QString &id, const QString &newParentId); void doMoveFolder(const QString &id, const QString &newParentId); void setupGeneratorMenu(); /** @brief Set focus to the Bin view. */ void focusBinView() const; /** @brief Get a string list of all clip ids that are inside a folder defined by id. */ QStringList getBinFolderClipIds(const QString &id) const; /** @brief Build a rename subclip command. */ void renameSubClipCommand(const QString &id, const QString &newName, const QString &oldName, int in, int out); /** @brief Rename a clip zone (subclip). */ void renameSubClip(const QString &id, const QString &newName, const QString &oldName, int in, int out); /** @brief Returns current project's timecode. */ Timecode projectTimecode() const; /** @brief Trigger timecode format refresh where needed. */ void updateTimecodeFormat(); /** @brief Edit an effect settings to a bin clip. */ void editMasterEffect(std::shared_ptr clip); /** @brief An effect setting was changed, update stack if displayed. */ void updateMasterEffect(ClipController *ctl); /** @brief Display a message about an operation in status bar. */ void emitMessage(const QString &, int, MessageType); void rebuildMenu(); void refreshIcons(); /** @brief This function change the global enabled state of the bin effects */ void setBinEffectsEnabled(bool enabled); void requestAudioThumbs(const QString &id, long duration); /** @brief Proxy status for the project changed, update. */ void refreshProxySettings(); /** @brief A clip is ready, update its info panel if displayed. */ void emitRefreshPanel(const QString &id); /** @brief Returns true if there is no clip. */ bool isEmpty() const; /** @brief Trigger reload of all clips. */ void reloadAllProducers(); /** @brief Get usage stats for project bin. */ void getBinStats(uint *used, uint *unused, qint64 *usedSize, qint64 *unusedSize); /** @brief Returns the clip properties dockwidget. */ QDockWidget *clipPropertiesDock(); /** @brief Returns a document's cache dir. ok is set to false if folder does not exist */ QDir getCacheDir(CacheType type, bool *ok) const; void rebuildProxies(); /** @brief Return a list of all clips hashes used in this project */ QStringList getProxyHashList(); /** @brief Get info (id, name) of a folder (or the currently selected one) */ const QStringList getFolderInfo(const QModelIndex &selectedIx = QModelIndex()); /** @brief Get binId of the current selected folder */ QString getCurrentFolder(); /** @brief Save a clip zone as MLT playlist */ void saveZone(const QStringList &info, const QDir &dir); // TODO refac: remove this and call directly the function in ProjectItemModel void cleanup(); private slots: void slotAddClip(); void slotReloadClip(); /** @brief Set sorting column */ void slotSetSorting(); /** @brief Show/hide date column */ void slotShowDateColumn(bool show); void slotShowDescColumn(bool show); /** @brief Setup the bin view type (icon view, tree view, ...). * @param action The action whose data defines the view type or nullptr to keep default view */ void slotInitView(QAction *action); /** @brief Update status for clip jobs */ void slotUpdateJobStatus(const QString &, int, int, const QString &label = QString(), const QString &actionName = QString(), const QString &details = QString()); void slotSetIconSize(int size); void selectProxyModel(const QModelIndex &id); void slotSaveHeaders(); void slotItemDropped(const QStringList &ids, const QModelIndex &parent); void slotItemDropped(const QList &urls, const QModelIndex &parent); void slotEffectDropped(const QStringList &effectData, const QModelIndex &parent); void slotItemEdited(const QModelIndex &, const QModelIndex &, const QVector &); /** @brief Reset all text and log data from info message widget. */ void slotResetInfoMessage(); /** @brief Show dialog prompting for removal of invalid clips. */ void slotQueryRemoval(const QString &id, const QString &url, const QString &errorMessage); /** @brief Request display of current clip in monitor. */ void slotOpenCurrent(); void slotZoomView(bool zoomIn); /** @brief Widget gained focus, make sure we display effects for master clip. */ void slotGotFocus(); /** @brief Rename a Bin Item. */ void slotRenameItem(); void doRefreshPanel(const QString &id); /** @brief Send audio thumb data to monitor for display. */ void slotSendAudioThumb(const QString &id); void doRefreshAudioThumbs(const QString &id); /** @brief Enable item view and hide message */ void slotMessageActionTriggered(); /** @brief Request editing of title or slideshow clip */ void slotEditClip(); /** @brief Enable / disable clear button on search line * this is a workaround foq Qt bug 54676 */ void showClearButton(bool show); public slots: void slotRemoveInvalidClip(const QString &id, bool replace, const QString &errorMessage); /** @brief Reload clip thumbnail - when frame for thumbnail changed */ void slotRefreshClipThumbnail(const QString &id); void slotDeleteClip(); void slotItemDoubleClicked(const QModelIndex &ix, const QPoint pos); void slotSwitchClipProperties(std::shared_ptr clip); void slotSwitchClipProperties(); /** @brief Creates a new folder with optional name, and returns new folder's id */ QString slotAddFolder(const QString &folderName = QString()); void slotCreateProjectClip(); void slotEditClipCommand(const QString &id, const QMap &oldProps, const QMap &newProps); /** @brief Start a filter job requested by a filter applied in timeline */ void slotStartFilterJob(const ItemInfo &info, const QString &id, QMap &filterParams, QMap &consumerParams, QMap &extraParams); /** @brief Open current clip in an external editing application */ void slotOpenClip(); void slotDuplicateClip(); void slotLocateClip(); /** @brief Add extra data to a clip. */ void slotAddClipExtraData(const QString &id, const QString &key, const QString &data = QString(), QUndoCommand *groupCommand = nullptr); void slotUpdateClipProperties(const QString &id, const QMap &properties, bool refreshPropertiesPanel); /** @brief Pass some important properties to timeline track producers. */ void updateTimelineProducers(const QString &id, const QMap &passProperties); /** @brief Add effect to active Bin clip (used when double clicking an effect in list). */ void slotAddEffect(QString id, const QStringList &effectData); /** @brief Request current frame from project monitor. * @param clipId is the id of a clip we want to hide from screenshot * @param request true to start capture process, false to end it. It is necessary to emit a false after image is received **/ void slotGetCurrentProjectImage(const QString &clipId, bool request); void slotExpandUrl(const ItemInfo &info, const QString &url, QUndoCommand *command); /** @brief Abort all ongoing operations to prepare close. */ void abortOperations(); void doDisplayMessage(const QString &text, KMessageWidget::MessageType type, const QList &actions = QList()); void doDisplayMessage(const QString &text, KMessageWidget::MessageType type, const QString &logInfo); /** @brief Reset all clip usage to 0 */ void resetUsageCount(); /** @brief Select a clip in the Bin from its id. */ void selectClipById(const QString &id, int frame = -1, const QPoint &zone = QPoint()); void slotAddClipToProject(const QUrl &url); void droppedUrls(const QList &urls, const QStringList &folderInfo = QStringList()); /** @brief Returns the effectstack of a given clip. */ std::shared_ptr getClipEffectStack(int itemId); /** @brief Returns the duration of a given clip. */ int getClipDuration(int itemId) const; /** @brief Returns the state of a given clip: AudioOnly, VideoOnly, Disabled (Disabled means it has audio and video capabilities */ PlaylistState::ClipState getClipState(int itemId) const; + /** @brief Adjust project profile to current clip. */ + void adjustProjectProfileToItem(); protected: /* This function is called whenever an item is selected to propagate signals (for ex request to show the clip in the monitor) */ void setCurrent(std::shared_ptr item); void selectClip(const std::shared_ptr &clip); void contextMenuEvent(QContextMenuEvent *event) override; bool eventFilter(QObject *obj, QEvent *event) override; private: std::shared_ptr m_itemModel; QAbstractItemView *m_itemView; /** @brief An "Up" item that is inserted in bin when using icon view so that user can navigate up */ std::shared_ptr m_folderUp; BinItemDelegate *m_binTreeViewDelegate; ProjectSortProxyModel *m_proxyModel; QToolBar *m_toolbar; KdenliveDoc *m_doc; QLineEdit *m_searchLine; QToolButton *m_addButton; QMenu *m_extractAudioAction; QMenu *m_transcodeAction; QMenu *m_clipsActionsMenu; QAction *m_inTimelineAction; QAction *m_showDate; QAction *m_showDesc; /** @brief Default view type (icon, tree, ...) */ BinViewType m_listType; /** @brief Default icon size for the views. */ QSize m_iconSize; /** @brief Keeps the column width info of the tree view. */ QByteArray m_headerInfo; QVBoxLayout *m_layout; QDockWidget *m_propertiesDock; QScrollArea *m_propertiesPanel; QSlider *m_slider; Monitor *m_monitor; QIcon m_blankThumb; QMenu *m_menu; QAction *m_openAction; QAction *m_editAction; QAction *m_reloadAction; QAction *m_duplicateAction; QAction *m_locateAction; QAction *m_proxyAction; QAction *m_deleteAction; QAction *m_renameAction; QMenu *m_jobsMenu; QAction *m_cancelJobs; QAction *m_discardCurrentClipJobs; QAction *m_discardPendingJobs; SmallJobLabel *m_infoLabel; /** @brief The info widget for failed jobs. */ KMessageWidget *m_infoMessage; QStringList m_errorLog; InvalidDialog *m_invalidClipDialog; /** @brief Set to true if widget just gained focus (means we have to update effect stack . */ bool m_gainedFocus; /** @brief List of Clip Ids that want an audio thumb. */ QStringList m_audioThumbsList; QString m_processingAudioThumb; QMutex m_audioThumbMutex; /** @brief Total number of milliseconds to process for audio thumbnails */ long m_audioDuration; /** @brief Total number of milliseconds already processed for audio thumbnails */ long m_processedAudio; /** @brief Indicates whether audio thumbnail creation is running. */ QFuture m_audioThumbsThread; void showClipProperties(std::shared_ptr clip, bool forceRefresh = false); /** @brief Get the QModelIndex value for an item in the Bin. */ QModelIndex getIndexForId(const QString &id, bool folderWanted) const; std::shared_ptr getFirstSelectedClip(); void showTitleWidget(std::shared_ptr clip); void showSlideshowWidget(std::shared_ptr clip); void processAudioThumbs(); signals: void itemUpdated(std::shared_ptr); void producerReady(const QString &id); /** @brief Save folder info into MLT. */ void storeFolder(const QString &folderId, const QString &parentId, const QString &oldParentId, const QString &folderName); void gotFilterJobResults(const QString &, int, int, stringMap, stringMap); /** @brief Trigger timecode format refresh where needed. */ void refreshTimeCode(); /** @brief Request display of effect stack for a Bin clip. */ void requestShowEffectStack(const QString &clipName, std::shared_ptr, QSize frameSize, bool showKeyframes); /** @brief Request that the given clip is displayed in the clip monitor */ void requestClipShow(std::shared_ptr); void displayBinMessage(const QString &, KMessageWidget::MessageType); void displayMessage(const QString &, int, MessageType); void requesteInvalidRemoval(const QString &, const QString &, const QString &); /** @brief Analysis data changed, refresh panel. */ void updateAnalysisData(const QString &); void openClip(std::shared_ptr c, int in = -1, int out = -1); /** @brief Fill context menu with occurrences of this clip in timeline. */ void findInTimeline(const QString &, QList ids = QList()); void clipNameChanged(const QString &); /** @brief A clip was updated, request panel update. */ void refreshPanel(const QString &id); }; #endif diff --git a/src/jobs/loadjob.cpp b/src/jobs/loadjob.cpp index f1c7fe9db..6da5bcff8 100644 --- a/src/jobs/loadjob.cpp +++ b/src/jobs/loadjob.cpp @@ -1,555 +1,555 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "loadjob.hpp" #include "bin/projectclip.h" #include "bin/projectfolder.h" #include "bin/projectitemmodel.h" #include "core.h" #include "doc/kdenlivedoc.h" #include "doc/kthumb.h" #include "kdenlivesettings.h" #include "klocalizedstring.h" #include "macros.hpp" #include "profiles/profilemodel.hpp" #include "project/dialogs/slideshowclip.h" #include "xml/xml.hpp" #include #include #include #include #include LoadJob::LoadJob(const QString &binId, const QDomElement &xml) : AbstractClipJob(LOADJOB, binId) , m_xml(xml) { } const QString LoadJob::getDescription() const { return i18n("Loading clip %1", m_clipId); } namespace { ClipType::ProducerType getTypeForService(const QString &id, const QString &path) { if (id.isEmpty()) { QString ext = path.section(QLatin1Char('.'), -1); if (ext == QLatin1String("mlt") || ext == QLatin1String("kdenlive")) { return ClipType::Playlist; } return ClipType::Unknown; } if (id == QLatin1String("color") || id == QLatin1String("colour")) { return ClipType::Color; } if (id == QLatin1String("kdenlivetitle")) { return ClipType::Text; } if (id == QLatin1String("qtext")) { return ClipType::QText; } if (id == QLatin1String("xml") || id == QLatin1String("consumer")) { return ClipType::Playlist; } if (id == QLatin1String("webvfx")) { return ClipType::WebVfx; } return ClipType::Unknown; } // Read the properties of the xml and pass them to the producer. Note that some properties like resource are ignored void processProducerProperties(std::shared_ptr prod, const QDomElement &xml) { // TODO: there is some duplication with clipcontroller > updateproducer that also copies properties QString value; QStringList internalProperties; internalProperties << QStringLiteral("bypassDuplicate") << QStringLiteral("resource") << QStringLiteral("mlt_service") << QStringLiteral("audio_index") << QStringLiteral("video_index") << QStringLiteral("mlt_type"); QDomNodeList props; if (xml.tagName() == QLatin1String("producer")) { props = xml.childNodes(); } else { props = xml.firstChildElement(QStringLiteral("producer")).childNodes(); } for (int i = 0; i < props.count(); ++i) { if (props.at(i).toElement().tagName() != QStringLiteral("property")) { continue; } QString propertyName = props.at(i).toElement().attribute(QStringLiteral("name")); if (!internalProperties.contains(propertyName) && !propertyName.startsWith(QLatin1Char('_'))) { value = props.at(i).firstChild().nodeValue(); if (propertyName.startsWith(QLatin1String("kdenlive-force."))) { // this is a special forced property, pass it propertyName.remove(0, 15); } prod->set(propertyName.toUtf8().constData(), value.toUtf8().constData()); } } } } // namespace // static std::shared_ptr LoadJob::loadResource(QString &resource, const QString &type) { if (!resource.startsWith(type)) { resource.prepend(type); } return std::make_shared(pCore->getCurrentProfile()->profile(), nullptr, resource.toUtf8().constData()); } std::shared_ptr LoadJob::loadPlaylist(QString &resource) { std::unique_ptr xmlProfile(new Mlt::Profile()); xmlProfile->set_explicit(0); std::unique_ptr producer(new Mlt::Producer(*xmlProfile, "xml", resource.toUtf8().constData())); if (!producer->is_valid()) { qDebug() << "////// ERROR, CANNOT LOAD SELECTED PLAYLIST: " << resource; return nullptr; } if (pCore->getCurrentProfile()->isCompatible(xmlProfile.get())) { // We can use the "xml" producer since profile is the same (using it with different profiles corrupts the project. // Beware that "consumer" currently crashes on audio mixes! resource.prepend(QStringLiteral("xml:")); } else { // This is currently crashing so I guess we'd better reject it for now qDebug() << "////// ERROR, INCOMPATIBLE PROFILE: " << resource; return nullptr; // path.prepend(QStringLiteral("consumer:")); } pCore->getCurrentProfile()->set_explicit(1); return std::make_shared(pCore->getCurrentProfile()->profile(), nullptr, resource.toUtf8().constData()); } -void LoadJob::checkProfile() +void LoadJob::checkProfile(const QString clipId, QDomElement xml, std::shared_ptr producer) { // Check if clip profile matches - QString service = m_producer->get("mlt_service"); + QString service = producer->get("mlt_service"); // Check for image producer if (service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) { // This is an image, create profile from image size - int width = m_producer->get_int("meta.media.width"); - int height = m_producer->get_int("meta.media.height"); + int width = producer->get_int("meta.media.width"); + int height = producer->get_int("meta.media.height"); if (width > 100 && height > 100) { std::unique_ptr projectProfile(new ProfileParam(pCore->getCurrentProfile().get())); projectProfile->m_width = width; projectProfile->m_height = height; projectProfile->m_sample_aspect_num = 1; projectProfile->m_sample_aspect_den = 1; projectProfile->m_display_aspect_num = width; projectProfile->m_display_aspect_den = height; projectProfile->m_description.clear(); - pCore->currentDoc()->switchProfile(projectProfile, m_clipId, m_xml); + pCore->currentDoc()->switchProfile(projectProfile, clipId, xml); } else { // Very small image, we probably don't want to use this as profile } } else if (service.contains(QStringLiteral("avformat"))) { std::unique_ptr blankProfile(new Mlt::Profile()); blankProfile->set_explicit(0); - blankProfile->from_producer(*m_producer); + blankProfile->from_producer(*producer); std::unique_ptr clipProfile(new ProfileParam(blankProfile.get())); std::unique_ptr projectProfile(new ProfileParam(pCore->getCurrentProfile().get())); clipProfile->adjustWidth(); if (*clipProfile.get() == *projectProfile.get()) { if (KdenliveSettings::default_profile().isEmpty()) { // Confirm default project format KdenliveSettings::setDefault_profile(pCore->getCurrentProfile()->path()); } } else { // Profiles do not match, propose profile adjustment - pCore->currentDoc()->switchProfile(clipProfile, m_clipId, m_xml); + pCore->currentDoc()->switchProfile(clipProfile, clipId, xml); } } } void LoadJob::processSlideShow() { int ttl = Xml::getXmlProperty(m_xml, QStringLiteral("ttl")).toInt(); QString anim = Xml::getXmlProperty(m_xml, QStringLiteral("animation")); if (!anim.isEmpty()) { auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "affine"); if ((filter != nullptr) && filter->is_valid()) { int cycle = ttl; QString geometry = SlideshowClip::animationToGeometry(anim, cycle); if (!geometry.isEmpty()) { if (anim.contains(QStringLiteral("low-pass"))) { auto *blur = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "boxblur"); if ((blur != nullptr) && blur->is_valid()) { m_producer->attach(*blur); } } filter->set("transition.geometry", geometry.toUtf8().data()); filter->set("transition.cycle", cycle); m_producer->attach(*filter); } } } QString fade = Xml::getXmlProperty(m_xml, QStringLiteral("fade")); if (fade == QLatin1String("1")) { // user wants a fade effect to slideshow auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "luma"); if ((filter != nullptr) && filter->is_valid()) { if (ttl != 0) { filter->set("cycle", ttl); } QString luma_duration = Xml::getXmlProperty(m_xml, QStringLiteral("luma_duration")); QString luma_file = Xml::getXmlProperty(m_xml, QStringLiteral("luma_file")); if (!luma_duration.isEmpty()) { filter->set("duration", luma_duration.toInt()); } if (!luma_file.isEmpty()) { filter->set("luma.resource", luma_file.toUtf8().constData()); QString softness = Xml::getXmlProperty(m_xml, QStringLiteral("softness")); if (!softness.isEmpty()) { int soft = softness.toInt(); filter->set("luma.softness", (double)soft / 100.0); } } m_producer->attach(*filter); } } QString crop = Xml::getXmlProperty(m_xml, QStringLiteral("crop")); if (crop == QLatin1String("1")) { // user wants to center crop the slides auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "crop"); if ((filter != nullptr) && filter->is_valid()) { filter->set("center", 1); m_producer->attach(*filter); } } } bool LoadJob::startJob() { if (m_done) { return true; } m_resource = Xml::getXmlProperty(m_xml, QStringLiteral("resource")); ClipType::ProducerType type = static_cast(m_xml.attribute(QStringLiteral("type")).toInt()); QString service = Xml::getXmlProperty(m_xml, QStringLiteral("mlt_service")); if (type == ClipType::Unknown) { type = getTypeForService(service, m_resource); } switch (type) { case ClipType::Color: m_producer = loadResource(m_resource, QStringLiteral("color:")); break; case ClipType::Text: case ClipType::TextTemplate: m_producer = loadResource(m_resource, QStringLiteral("kdenlivetitle:")); break; case ClipType::QText: m_producer = loadResource(m_resource, QStringLiteral("qtext:")); break; case ClipType::Playlist: m_producer = loadPlaylist(m_resource); break; case ClipType::SlideShow: default: if (!service.isEmpty()) { service.append(QChar(':')); m_producer = loadResource(m_resource, service); } else { m_producer = std::make_shared(pCore->getCurrentProfile()->profile(), nullptr, m_resource.toUtf8().constData()); } break; } if (!m_producer || m_producer->is_blank() || !m_producer->is_valid()) { qCDebug(KDENLIVE_LOG) << " / / / / / / / / ERROR / / / / // CANNOT LOAD PRODUCER: " << m_resource; m_done = true; m_successful = false; if (m_producer) { m_producer.reset(); } QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(const QString &, i18n("Cannot open file %1", m_resource)), Q_ARG(int, (int)KMessageWidget::Warning)); m_errorMessage.append(i18n("ERROR: Could not load clip %1: producer is invalid", m_resource)); return false; } processProducerProperties(m_producer, m_xml); QString clipName = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:clipname")); if (clipName.isEmpty()) { clipName = QFileInfo(Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:originalurl"))).fileName(); } m_producer->set("kdenlive:clipname", clipName.toUtf8().constData()); QString groupId = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:folderid")); if (!groupId.isEmpty()) { m_producer->set("kdenlive:folderid", groupId.toUtf8().constData()); } int clipOut = 0, duration = 0; if (m_xml.hasAttribute(QStringLiteral("out"))) { clipOut = m_xml.attribute(QStringLiteral("out")).toInt(); } // setup length here as otherwise default length (currently 15000 frames in MLT) will be taken even if outpoint is larger if (type == ClipType::Color || type == ClipType::Text || type == ClipType::TextTemplate || type == ClipType::QText || type == ClipType::Image || type == ClipType::SlideShow) { int length; if (m_xml.hasAttribute(QStringLiteral("length"))) { length = m_xml.attribute(QStringLiteral("length")).toInt(); clipOut = qMax(1, length - 1); } else { length = Xml::getXmlProperty(m_xml, QStringLiteral("length")).toInt(); clipOut -= m_xml.attribute(QStringLiteral("in")).toInt(); if (length < clipOut) { length = clipOut == 1 ? 1 : clipOut + 1; } } // Pass duration if it was forced if (m_xml.hasAttribute(QStringLiteral("duration"))) { duration = m_xml.attribute(QStringLiteral("duration")).toInt(); if (length < duration) { length = duration; if (clipOut > 0) { clipOut = length - 1; } } } if (duration == 0) { duration = length; } m_producer->set("length", length); int kdenlive_duration = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:duration")).toInt(); m_producer->set("kdenlive:duration", kdenlive_duration > 0 ? kdenlive_duration : length); } if (clipOut > 0) { m_producer->set_in_and_out(m_xml.attribute(QStringLiteral("in")).toInt(), clipOut); } if (m_xml.hasAttribute(QStringLiteral("templatetext"))) { m_producer->set("templatetext", m_xml.attribute(QStringLiteral("templatetext")).toUtf8().constData()); } duration = duration > 0 ? duration : m_producer->get_playtime(); if (type == ClipType::SlideShow) { processSlideShow(); } int vindex = -1; double fps = -1; const QString mltService = m_producer->get("mlt_service"); if (mltService == QLatin1String("xml") || mltService == QLatin1String("consumer")) { // MLT playlist, create producer with blank profile to get real profile info QString tmpPath = m_resource; if (tmpPath.startsWith(QLatin1String("consumer:"))) { tmpPath = "xml:" + tmpPath.section(QLatin1Char(':'), 1); } Mlt::Profile original_profile; std::unique_ptr tmpProd(new Mlt::Producer(original_profile, nullptr, tmpPath.toUtf8().constData())); original_profile.set_explicit(1); double originalFps = original_profile.fps(); fps = originalFps; if (originalFps > 0 && !qFuzzyCompare(originalFps, pCore->getCurrentFps())) { int originalLength = tmpProd->get_length(); int fixedLength = (int)(originalLength * pCore->getCurrentFps() / originalFps); m_producer->set("length", fixedLength); m_producer->set("out", fixedLength - 1); } } else if (mltService == QLatin1String("avformat")) { // check if there are multiple streams vindex = m_producer->get_int("video_index"); // List streams int streams = m_producer->get_int("meta.media.nb_streams"); m_audio_list.clear(); m_video_list.clear(); for (int i = 0; i < streams; ++i) { QByteArray propertyName = QStringLiteral("meta.media.%1.stream.type").arg(i).toLocal8Bit(); QString stype = m_producer->get(propertyName.data()); if (stype == QLatin1String("audio")) { m_audio_list.append(i); } else if (stype == QLatin1String("video")) { m_video_list.append(i); } } if (vindex > -1) { char property[200]; snprintf(property, sizeof(property), "meta.media.%d.stream.frame_rate", vindex); fps = m_producer->get_double(property); } if (fps <= 0) { if (m_producer->get_double("meta.media.frame_rate_den") > 0) { fps = m_producer->get_double("meta.media.frame_rate_num") / m_producer->get_double("meta.media.frame_rate_den"); } else { fps = m_producer->get_double("source_fps"); } } } if (fps <= 0 && type == ClipType::Unknown) { // something wrong, maybe audio file with embedded image QMimeDatabase db; QString mime = db.mimeTypeForFile(m_resource).name(); if (mime.startsWith(QLatin1String("audio"))) { m_producer->set("video_index", -1); vindex = -1; } } m_done = m_successful = true; return true; } void LoadJob::processMultiStream() { auto m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); // We retrieve the folder containing our clip, because we will set the other streams in the same auto parent = pCore->projectItemModel()->getRootFolder()->clipId(); if (auto ptr = m_binClip->parentItem().lock()) { parent = std::static_pointer_cast(ptr)->clipId(); } else { qDebug() << "Warning, something went wrong while accessing parent of bin clip"; } // This helper lambda request addition of a given stream auto addStream = [ this, parentId = std::move(parent) ](int vindex, int aindex, Fun &undo, Fun &redo) { auto clone = ProjectClip::cloneProducer(m_producer); clone->set("video_index", vindex); clone->set("audio_index", aindex); QString id; pCore->projectItemModel()->requestAddBinClip(id, clone, parentId, undo, redo); }; Fun undo = []() { return true; }; Fun redo = []() { return true; }; if (KdenliveSettings::automultistreams()) { for (int i = 1; i < m_video_list.count(); ++i) { int vindex = m_video_list.at(i); int aindex = 0; if (i <= m_audio_list.count() - 1) { aindex = m_audio_list.at(i); } addStream(vindex, aindex, undo, redo); } return; } int width = 60.0 * pCore->getCurrentDar(); if (width % 2 == 1) { width++; } QScopedPointer dialog(new QDialog(qApp->activeWindow())); dialog->setWindowTitle(QStringLiteral("Multi Stream Clip")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QWidget *mainWidget = new QWidget(dialog.data()); auto *mainLayout = new QVBoxLayout; dialog->setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dialog->connect(buttonBox, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept); dialog->connect(buttonBox, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject); okButton->setText(i18n("Import selected clips")); QLabel *lab1 = new QLabel(i18n("Additional streams for clip\n %1", m_resource), mainWidget); mainLayout->addWidget(lab1); QList groupList; QList comboList; // We start loading the list at 1, video index 0 should already be loaded for (int j = 1; j < m_video_list.count(); ++j) { m_producer->set("video_index", m_video_list.at(j)); // TODO this keyframe should be cached QImage thumb = KThumb::getFrame(m_producer.get(), 0, width, 60); QGroupBox *streamFrame = new QGroupBox(i18n("Video stream %1", m_video_list.at(j)), mainWidget); mainLayout->addWidget(streamFrame); streamFrame->setProperty("vindex", m_video_list.at(j)); groupList << streamFrame; streamFrame->setCheckable(true); streamFrame->setChecked(true); auto *vh = new QVBoxLayout(streamFrame); QLabel *iconLabel = new QLabel(mainWidget); mainLayout->addWidget(iconLabel); iconLabel->setPixmap(QPixmap::fromImage(thumb)); vh->addWidget(iconLabel); if (m_audio_list.count() > 1) { auto *cb = new KComboBox(mainWidget); mainLayout->addWidget(cb); for (int k = 0; k < m_audio_list.count(); ++k) { cb->addItem(i18n("Audio stream %1", m_audio_list.at(k)), m_audio_list.at(k)); } comboList << cb; cb->setCurrentIndex(qMin(j, m_audio_list.count() - 1)); vh->addWidget(cb); } mainLayout->addWidget(streamFrame); } mainLayout->addWidget(buttonBox); if (dialog->exec() == QDialog::Accepted) { // import selected streams for (int i = 0; i < groupList.count(); ++i) { if (groupList.at(i)->isChecked()) { int vindex = groupList.at(i)->property("vindex").toInt(); int ax = qMin(i, comboList.size() - 1); int aindex = -1; if (ax >= 0) { // only check audio index if we have several audio streams aindex = comboList.at(ax)->itemData(comboList.at(ax)->currentIndex()).toInt(); } addStream(vindex, aindex, undo, redo); } } } pCore->pushUndo(undo, redo, i18n("Add additional streams for clip")); } bool LoadJob::commitResult(Fun &undo, Fun &redo) { qDebug() << "################### loadjob COMMIT"; Q_ASSERT(!m_resultConsumed); if (!m_done) { qDebug() << "ERROR: Trying to consume invalid results"; return false; } m_resultConsumed = true; auto m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); if (!m_successful) { // TODO: Deleting cannot happen at this stage or we endup in a mutex lock pCore->projectItemModel()->requestBinClipDeletion(m_binClip, undo, redo); return false; } if (m_xml.hasAttribute(QStringLiteral("_checkProfile")) && m_producer->get_int("video_index") > -1) { - checkProfile(); + checkProfile(m_clipId, m_xml, m_producer); } if (m_video_list.size() > 1) { processMultiStream(); } // note that the image is moved into lambda, it won't be available from this class anymore auto operation = [ clip = m_binClip, prod = std::move(m_producer) ]() { clip->setProducer(prod, true); return true; }; auto reverse = []() { // This is probably not invertible, leave as is. return true; }; bool ok = operation(); if (ok) { if (pCore->projectItemModel()->clipsCount() == 1) { // Always select first added clip pCore->selectBinClip(m_clipId); } UPDATE_UNDO_REDO_NOLOCK(operation, reverse, undo, redo); } return ok; } diff --git a/src/jobs/loadjob.hpp b/src/jobs/loadjob.hpp index 2dcd43400..1fc96232a 100644 --- a/src/jobs/loadjob.hpp +++ b/src/jobs/loadjob.hpp @@ -1,80 +1,80 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #pragma once #include "abstractclipjob.h" #include #include /* @brief This class represents the job that corresponds to loading a clip from xml */ class ProjectClip; namespace Mlt { class Producer; } class LoadJob : public AbstractClipJob { Q_OBJECT public: /* @brief Extract a thumb for given clip. @param frameNumber is the frame to extract. Leave to -1 for default @param persistent: if true, we will use the persistent cache (for query and saving) */ LoadJob(const QString &binId, const QDomElement &xml); const QString getDescription() const override; bool startJob() override; /** @brief This is to be called after the job finished. By design, the job should store the result of the computation but not share it with the rest of the code. This happens when we call commitResult */ bool commitResult(Fun &undo, Fun &redo) override; + // Do some checks on the profile + static void checkProfile(const QString clipId, QDomElement xml, std::shared_ptr producer); + protected: // helper to load some kind of resources such as color. This will modify resource if needs be (for eg., in the case of color, it will prepend "color:" if // needed) static std::shared_ptr loadResource(QString &resource, const QString &type); std::shared_ptr loadPlaylist(QString &resource); - // Do some checks on the profile - void checkProfile(); - // Create the required filter for a slideshow void processSlideShow(); // This should be called from commitResult (that is, from the GUI thread) to deal with multi stream videos void processMultiStream(); private: QDomElement m_xml; bool m_done{false}, m_successful{false}; std::shared_ptr m_producer; QList m_audio_list, m_video_list; QString m_resource; }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 97c62bc73..03c90d559 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,3791 +1,3790 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "mainwindow.h" #include "assets/assetpanel.hpp" #include "bin/clipcreator.hpp" #include "bin/generators/generators.h" #include "bin/projectclip.h" #include "bin/projectfolder.h" #include "bin/projectitemmodel.h" #include "core.h" #include "dialogs/clipcreationdialog.h" #include "dialogs/kdenlivesettingsdialog.h" #include "dialogs/renderwidget.h" #include "dialogs/wizard.h" #include "doc/docundostack.hpp" #include "doc/kdenlivedoc.h" #include "effects/effectlist/view/effectlistwidget.hpp" #include "effectslist/effectbasket.h" #include "hidetitlebars.h" #include "jobs/jobmanager.h" #include "jobs/scenesplitjob.hpp" #include "jobs/speedjob.hpp" #include "jobs/stabilizejob.hpp" #include "kdenlivesettings.h" #include "layoutmanagement.h" #include "library/librarywidget.h" #include "mainwindowadaptor.h" #include "mltconnection.h" #include "mltcontroller/clipcontroller.h" #include "monitor/monitor.h" #include "monitor/monitormanager.h" #include "monitor/scopes/audiographspectrum.h" #include "profiles/profilemodel.hpp" #include "project/cliptranscode.h" #include "project/dialogs/archivewidget.h" #include "project/dialogs/projectsettings.h" #include "project/projectcommands.h" #include "project/projectmanager.h" #include "scopes/scopemanager.h" #include "timeline2/view/timelinecontroller.h" #include "timeline2/view/timelinetabs.hpp" #include "timeline2/view/timelinewidget.h" #include "titler/titlewidget.h" #include "transitions/transitionlist/view/transitionlistwidget.hpp" #include "transitions/transitionsrepository.hpp" #include "utils/resourcewidget.h" #include "utils/thememanager.h" #include "profiles/profilerepository.hpp" #include "widgets/progressbutton.h" #include #include "project/dialogs/temporarydata.h" #ifdef USE_JOGSHUTTLE #include "jogshuttle/jogmanager.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char version[] = KDENLIVE_VERSION; namespace Mlt { class Producer; } QMap MainWindow::m_lumacache; QMap MainWindow::m_lumaFiles; /*static bool sortByNames(const QPair &a, const QPair &b) { return a.first < b.first; }*/ // determine the default KDE style as defined BY THE USER // (as opposed to whatever style KDE considers default) static QString defaultStyle(const char *fallback = nullptr) { KSharedConfigPtr kdeGlobals = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup cg(kdeGlobals, "KDE"); return cg.readEntry("widgetStyle", fallback); } MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent) , m_exitCode(EXIT_SUCCESS) , m_assetPanel(nullptr) , m_clipMonitor(nullptr) , m_projectMonitor(nullptr) , m_timelineTabs(nullptr) , m_renderWidget(nullptr) , m_messageLabel(nullptr) , m_themeInitialized(false) , m_isDarkTheme(false) { } void MainWindow::init() { QString desktopStyle = QApplication::style()->objectName(); // Load themes auto themeManager = new ThemeManager(actionCollection()); actionCollection()->addAction(QStringLiteral("themes_menu"), themeManager); connect(themeManager, &ThemeManager::themeChanged, this, &MainWindow::slotThemeChanged); if (!KdenliveSettings::widgetstyle().isEmpty() && QString::compare(desktopStyle, KdenliveSettings::widgetstyle(), Qt::CaseInsensitive) != 0) { // User wants a custom widget style, init doChangeStyle(); } // Widget themes for non KDE users KActionMenu *stylesAction = new KActionMenu(i18n("Style"), this); auto *stylesGroup = new QActionGroup(stylesAction); // GTK theme does not work well with Kdenlive, and does not support color theming, so avoid it QStringList availableStyles = QStyleFactory::keys(); if (KdenliveSettings::widgetstyle().isEmpty()) { // First run QStringList incompatibleStyles; incompatibleStyles << QStringLiteral("GTK+") << QStringLiteral("windowsvista") << QStringLiteral("windowsxp"); if (incompatibleStyles.contains(desktopStyle, Qt::CaseInsensitive)) { if (availableStyles.contains(QStringLiteral("breeze"), Qt::CaseInsensitive)) { // Auto switch to Breeze theme KdenliveSettings::setWidgetstyle(QStringLiteral("Breeze")); } else if (availableStyles.contains(QStringLiteral("fusion"), Qt::CaseInsensitive)) { KdenliveSettings::setWidgetstyle(QStringLiteral("Fusion")); } } else { KdenliveSettings::setWidgetstyle(QStringLiteral("Default")); } } // Add default style action QAction *defaultStyle = new QAction(i18n("Default"), stylesGroup); defaultStyle->setData(QStringLiteral("Default")); defaultStyle->setCheckable(true); stylesAction->addAction(defaultStyle); if (KdenliveSettings::widgetstyle() == QLatin1String("Default") || KdenliveSettings::widgetstyle().isEmpty()) { defaultStyle->setChecked(true); } for (const QString &style : availableStyles) { auto *a = new QAction(style, stylesGroup); a->setCheckable(true); a->setData(style); if (KdenliveSettings::widgetstyle() == style) { a->setChecked(true); } stylesAction->addAction(a); } connect(stylesGroup, &QActionGroup::triggered, this, &MainWindow::slotChangeStyle); // QIcon::setThemeSearchPaths(QStringList() <setCurrentProfile(defaultProfile.isEmpty() ? ProjectManager::getDefaultProjectFormat() : defaultProfile); m_commandStack = new QUndoGroup(); // If using a custom profile, make sure the file exists or fallback to default QString currentProfilePath = pCore->getCurrentProfile()->path(); if (currentProfilePath.startsWith(QLatin1Char('/')) && !QFile::exists(currentProfilePath)) { KMessageBox::sorry(this, i18n("Cannot find your default profile, switching to ATSC 1080p 25")); pCore->setCurrentProfile(QStringLiteral("atsc_1080p_25")); KdenliveSettings::setDefault_profile(QStringLiteral("atsc_1080p_25")); } m_gpuAllowed = EffectsRepository::get()->hasInternalEffect(QStringLiteral("glsl.manager")); m_shortcutRemoveFocus = new QShortcut(QKeySequence(QStringLiteral("Esc")), this); connect(m_shortcutRemoveFocus, &QShortcut::activated, this, &MainWindow::slotRemoveFocus); /// Add Widgets setDockOptions(dockOptions() | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks); setDockOptions(dockOptions() | QMainWindow::GroupedDragging); setTabPosition(Qt::AllDockWidgetAreas, (QTabWidget::TabPosition)KdenliveSettings::tabposition()); m_timelineToolBar = toolBar(QStringLiteral("timelineToolBar")); m_timelineToolBarContainer = new QWidget(this); auto *ctnLay = new QVBoxLayout; ctnLay->setSpacing(0); ctnLay->setContentsMargins(0, 0, 0, 0); m_timelineToolBarContainer->setLayout(ctnLay); ctnLay->addWidget(m_timelineToolBar); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup mainConfig(config, QStringLiteral("MainWindow")); KConfigGroup tbGroup(&mainConfig, QStringLiteral("Toolbar timelineToolBar")); m_timelineToolBar->applySettings(tbGroup); QFrame *fr = new QFrame(this); fr->setFrameShape(QFrame::HLine); fr->setMaximumHeight(1); fr->setLineWidth(1); ctnLay->addWidget(fr); setCentralWidget(m_timelineToolBarContainer); setupActions(); QDockWidget *libraryDock = addDock(i18n("Library"), QStringLiteral("library"), pCore->library()); m_clipMonitor = new Monitor(Kdenlive::ClipMonitor, pCore->monitorManager(), this); pCore->bin()->setMonitor(m_clipMonitor); connect(m_clipMonitor, &Monitor::showConfigDialog, this, &MainWindow::slotPreferences); connect(m_clipMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly); connect(m_clipMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteClipMarker); connect(m_clipMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind); connect(m_clipMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward); connect(pCore->bin(), &Bin::findInTimeline, this, &MainWindow::slotClipInTimeline); // TODO deprecated, replace with Bin methods if necessary /*connect(m_projectList, SIGNAL(loadingIsOver()), this, SLOT(slotElapsedTime())); connect(m_projectList, SIGNAL(updateRenderStatus()), this, SLOT(slotCheckRenderStatus())); connect(m_projectList, SIGNAL(updateProfile(QString)), this, SLOT(slotUpdateProjectProfile(QString))); connect(m_projectList, SIGNAL(refreshClip(QString,bool)), pCore->monitorManager(), SLOT(slotRefreshCurrentMonitor(QString))); connect(m_clipMonitor, SIGNAL(zoneUpdated(QPoint)), m_projectList, SLOT(slotUpdateClipCut(QPoint)));*/ // TODO refac : reimplement ? // connect(m_clipMonitor, &Monitor::extractZone, pCore->bin(), &Bin::slotStartCutJob); connect(m_clipMonitor, &Monitor::passKeyPress, this, &MainWindow::triggerKey); m_projectMonitor = new Monitor(Kdenlive::ProjectMonitor, pCore->monitorManager(), this); connect(m_projectMonitor, &Monitor::passKeyPress, this, &MainWindow::triggerKey); connect(m_projectMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly); connect(m_projectMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteGuide); connect(m_projectMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind); connect(m_projectMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward); connect(m_loopClip, &QAction::triggered, m_projectMonitor, &Monitor::slotLoopClip); pCore->monitorManager()->initMonitors(m_clipMonitor, m_projectMonitor); connect(m_clipMonitor, &Monitor::addMasterEffect, pCore->bin(), &Bin::slotAddEffect); m_timelineTabs = new TimelineTabs(this); ctnLay->addWidget(m_timelineTabs); // Audio spectrum scope m_audioSpectrum = new AudioGraphSpectrum(pCore->monitorManager()); QDockWidget *spectrumDock = addDock(i18n("Audio Spectrum"), QStringLiteral("audiospectrum"), m_audioSpectrum); // Close library and audiospectrum on first run libraryDock->close(); spectrumDock->close(); m_projectBinDock = addDock(i18n("Project Bin"), QStringLiteral("project_bin"), pCore->bin()); m_assetPanel = new AssetPanel(this); connect(m_assetPanel, &AssetPanel::doSplitEffect, m_projectMonitor, &Monitor::slotSwitchCompare); connect(m_assetPanel, &AssetPanel::doSplitBinEffect, m_clipMonitor, &Monitor::slotSwitchCompare); connect(m_assetPanel, &AssetPanel::changeSpeed, this, &MainWindow::slotChangeSpeed); connect(m_timelineTabs, &TimelineTabs::showTransitionModel, m_assetPanel, &AssetPanel::showTransition); connect(m_timelineTabs, &TimelineTabs::showItemEffectStack, m_assetPanel, &AssetPanel::showEffectStack); connect(m_timelineTabs, &TimelineTabs::updateZoom, this, &MainWindow::updateZoomSlider); connect(pCore->bin(), &Bin::requestShowEffectStack, m_assetPanel, &AssetPanel::showEffectStack); connect(this, &MainWindow::clearAssetPanel, m_assetPanel, &AssetPanel::clearAssetPanel); connect(m_assetPanel, &AssetPanel::seekToPos, [this](int pos) { ObjectId oId = m_assetPanel->effectStackOwner(); switch (oId.first) { case ObjectType::TimelineTrack: case ObjectType::TimelineClip: case ObjectType::TimelineComposition: getCurrentTimeline()->controller()->setPosition(pos); break; case ObjectType::BinClip: m_clipMonitor->requestSeek(pos); break; default: qDebug() << "ERROR unhandled object type"; break; } }); m_effectStackDock = addDock(i18n("Properties"), QStringLiteral("effect_stack"), m_assetPanel); m_effectList2 = new EffectListWidget(this); connect(m_effectList2, &EffectListWidget::activateAsset, pCore->projectManager(), &ProjectManager::activateAsset); connect(m_assetPanel, &AssetPanel::reloadEffect, m_effectList2, &EffectListWidget::reloadCustomEffect); m_effectListDock = addDock(i18n("Effects"), QStringLiteral("effect_list"), m_effectList2); m_transitionList2 = new TransitionListWidget(this); m_transitionListDock = addDock(i18n("Transitions"), QStringLiteral("transition_list"), m_transitionList2); // Add monitors here to keep them at the right of the window m_clipMonitorDock = addDock(i18n("Clip Monitor"), QStringLiteral("clip_monitor"), m_clipMonitor); m_projectMonitorDock = addDock(i18n("Project Monitor"), QStringLiteral("project_monitor"), m_projectMonitor); m_undoView = new QUndoView(); m_undoView->setCleanIcon(QIcon::fromTheme(QStringLiteral("edit-clear"))); m_undoView->setEmptyLabel(i18n("Clean")); m_undoView->setGroup(m_commandStack); m_undoViewDock = addDock(i18n("Undo History"), QStringLiteral("undo_history"), m_undoView); // Color and icon theme stuff connect(m_commandStack, &QUndoGroup::cleanChanged, m_saveAction, &QAction::setDisabled); addAction(QStringLiteral("styles_menu"), stylesAction); QAction *iconAction = new QAction(i18n("Force Breeze Icon Theme"), this); iconAction->setCheckable(true); iconAction->setChecked(KdenliveSettings::force_breeze()); addAction(QStringLiteral("force_icon_theme"), iconAction); connect(iconAction, &QAction::triggered, this, &MainWindow::forceIconSet); // Close non-general docks for the initial layout // only show important ones m_undoViewDock->close(); /// Tabify Widgets tabifyDockWidget(m_transitionListDock, m_effectListDock); tabifyDockWidget(m_effectStackDock, pCore->bin()->clipPropertiesDock()); // tabifyDockWidget(m_effectListDock, m_effectStackDock); tabifyDockWidget(m_clipMonitorDock, m_projectMonitorDock); bool firstRun = readOptions(); // Monitor Record action addAction(QStringLiteral("switch_monitor_rec"), m_clipMonitor->recAction()); // Build effects menu m_effectsMenu = new QMenu(i18n("Add Effect"), this); m_effectActions = new KActionCategory(i18n("Effects"), actionCollection()); m_effectList2->reloadEffectMenu(m_effectsMenu, m_effectActions); m_transitionsMenu = new QMenu(i18n("Add Transition"), this); m_transitionActions = new KActionCategory(i18n("Transitions"), actionCollection()); auto *scmanager = new ScopeManager(this); new LayoutManagement(this); new HideTitleBars(this); m_extraFactory = new KXMLGUIClient(this); buildDynamicActions(); // Create Effect Basket (dropdown list of favorites) m_effectBasket = new EffectBasket(this); connect(m_effectBasket, &EffectBasket::activateAsset, pCore->projectManager(), &ProjectManager::activateAsset); connect(m_effectList2, &EffectListWidget::reloadFavorites, m_effectBasket, &EffectBasket::slotReloadBasket); auto *widgetlist = new QWidgetAction(this); widgetlist->setDefaultWidget(m_effectBasket); // widgetlist->setText(i18n("Favorite Effects")); widgetlist->setToolTip(i18n("Favorite Effects")); widgetlist->setIcon(QIcon::fromTheme(QStringLiteral("favorite"))); auto *menu = new QMenu(this); menu->addAction(widgetlist); auto *basketButton = new QToolButton(this); basketButton->setMenu(menu); basketButton->setToolButtonStyle(toolBar()->toolButtonStyle()); basketButton->setDefaultAction(widgetlist); basketButton->setPopupMode(QToolButton::InstantPopup); // basketButton->setText(i18n("Favorite Effects")); basketButton->setToolTip(i18n("Favorite Effects")); basketButton->setIcon(QIcon::fromTheme(QStringLiteral("favorite"))); auto *toolButtonAction = new QWidgetAction(this); toolButtonAction->setText(i18n("Favorite Effects")); toolButtonAction->setIcon(QIcon::fromTheme(QStringLiteral("favorite"))); toolButtonAction->setDefaultWidget(basketButton); addAction(QStringLiteral("favorite_effects"), toolButtonAction); connect(toolButtonAction, &QAction::triggered, basketButton, &QToolButton::showMenu); // Render button ProgressButton *timelineRender = new ProgressButton(i18n("Render"), 100, this); auto *tlrMenu = new QMenu(this); timelineRender->setMenu(tlrMenu); connect(this, &MainWindow::setRenderProgress, timelineRender, &ProgressButton::setProgress); auto *renderButtonAction = new QWidgetAction(this); renderButtonAction->setText(i18n("Render Button")); renderButtonAction->setIcon(QIcon::fromTheme(QStringLiteral("media-record"))); renderButtonAction->setDefaultWidget(timelineRender); addAction(QStringLiteral("project_render_button"), renderButtonAction); // Timeline preview button ProgressButton *timelinePreview = new ProgressButton(i18n("Rendering preview"), 1000, this); auto *tlMenu = new QMenu(this); timelinePreview->setMenu(tlMenu); connect(this, &MainWindow::setPreviewProgress, timelinePreview, &ProgressButton::setProgress); auto *previewButtonAction = new QWidgetAction(this); previewButtonAction->setText(i18n("Timeline Preview")); previewButtonAction->setIcon(QIcon::fromTheme(QStringLiteral("preview-render-on"))); previewButtonAction->setDefaultWidget(timelinePreview); addAction(QStringLiteral("timeline_preview_button"), previewButtonAction); setupGUI(KXmlGuiWindow::ToolBar | KXmlGuiWindow::StatusBar | KXmlGuiWindow::Save | KXmlGuiWindow::Create); if (firstRun) { QScreen *current = QApplication::primaryScreen(); if (current) { if (current->availableSize().height() < 1000) { resize(current->availableSize()); } else { resize(current->availableSize() / 1.5); } } } updateActionsToolTip(); m_timelineToolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle); m_timelineToolBar->setProperty("otherToolbar", true); timelinePreview->setToolButtonStyle(m_timelineToolBar->toolButtonStyle()); connect(m_timelineToolBar, &QToolBar::toolButtonStyleChanged, timelinePreview, &ProgressButton::setToolButtonStyle); timelineRender->setToolButtonStyle(toolBar()->toolButtonStyle()); /*ScriptingPart* sp = new ScriptingPart(this, QStringList()); guiFactory()->addClient(sp);*/ loadGenerators(); loadDockActions(); loadClipActions(); // Connect monitor overlay info menu. QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this)); connect(monitorOverlay, &QMenu::triggered, this, &MainWindow::slotSwitchMonitorOverlay); m_projectMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, nullptr, m_loopClip); m_clipMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, static_cast(factory()->container(QStringLiteral("marker_menu"), this))); QMenu *clipInTimeline = static_cast(factory()->container(QStringLiteral("clip_in_timeline"), this)); clipInTimeline->setIcon(QIcon::fromTheme(QStringLiteral("go-jump"))); pCore->bin()->setupGeneratorMenu(); connect(pCore->monitorManager(), &MonitorManager::updateOverlayInfos, this, &MainWindow::slotUpdateMonitorOverlays); // Setup and fill effects and transitions menus. QMenu *m = static_cast(factory()->container(QStringLiteral("video_effects_menu"), this)); connect(m, &QMenu::triggered, this, &MainWindow::slotAddEffect); connect(m_effectsMenu, &QMenu::triggered, this, &MainWindow::slotAddEffect); connect(m_transitionsMenu, &QMenu::triggered, this, &MainWindow::slotAddTransition); m_timelineContextMenu = new QMenu(this); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("insert_space"))); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("delete_space"))); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("delete_space_all_tracks"))); m_timelineContextMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Paste))); QMenu *markersMenu = static_cast(factory()->container(QStringLiteral("marker_menu"), this)); /*m_timelineClipActions->addMenu(markersMenu); m_timelineClipActions->addSeparator(); m_timelineClipActions->addMenu(m_transitionsMenu); m_timelineClipActions->addMenu(m_effectsMenu);*/ slotConnectMonitors(); m_timelineToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); // TODO: let user select timeline toolbar toolbutton style // connect(toolBar(), &QToolBar::iconSizeChanged, m_timelineToolBar, &QToolBar::setToolButtonStyle); m_timelineToolBar->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_timelineToolBar, &QWidget::customContextMenuRequested, this, &MainWindow::showTimelineToolbarMenu); QAction *prevRender = actionCollection()->action(QStringLiteral("prerender_timeline_zone")); QAction *stopPrevRender = actionCollection()->action(QStringLiteral("stop_prerender_timeline")); tlMenu->addAction(stopPrevRender); tlMenu->addAction(actionCollection()->action(QStringLiteral("set_render_timeline_zone"))); tlMenu->addAction(actionCollection()->action(QStringLiteral("unset_render_timeline_zone"))); tlMenu->addAction(actionCollection()->action(QStringLiteral("clear_render_timeline_zone"))); // Automatic timeline preview action QAction *autoRender = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Automatic Preview"), this); autoRender->setCheckable(true); autoRender->setChecked(KdenliveSettings::autopreview()); connect(autoRender, &QAction::triggered, this, &MainWindow::slotToggleAutoPreview); tlMenu->addAction(autoRender); tlMenu->addSeparator(); tlMenu->addAction(actionCollection()->action(QStringLiteral("disable_preview"))); tlMenu->addAction(actionCollection()->action(QStringLiteral("manage_cache"))); timelinePreview->defineDefaultAction(prevRender, stopPrevRender); timelinePreview->setAutoRaise(true); QAction *showRender = actionCollection()->action(QStringLiteral("project_render")); tlrMenu->addAction(showRender); tlrMenu->addAction(actionCollection()->action(QStringLiteral("stop_project_render"))); timelineRender->defineDefaultAction(showRender, showRender); timelineRender->setAutoRaise(true); // Populate encoding profiles KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation); /*KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation); if (KdenliveSettings::proxyparams().isEmpty() || KdenliveSettings::proxyextension().isEmpty()) { KConfigGroup group(&conf, "proxy"); QMap values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString proxystring = i.value(); KdenliveSettings::setProxyparams(proxystring.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setProxyextension(proxystring.section(QLatin1Char(';'), 1, 1)); } }*/ if (KdenliveSettings::v4l_parameters().isEmpty() || KdenliveSettings::v4l_extension().isEmpty()) { KConfigGroup group(&conf, "video4linux"); QMap values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString v4lstring = i.value(); KdenliveSettings::setV4l_parameters(v4lstring.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setV4l_extension(v4lstring.section(QLatin1Char(';'), 1, 1)); } } if (KdenliveSettings::grab_parameters().isEmpty() || KdenliveSettings::grab_extension().isEmpty()) { KConfigGroup group(&conf, "screengrab"); QMap values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString grabstring = i.value(); KdenliveSettings::setGrab_parameters(grabstring.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setGrab_extension(grabstring.section(QLatin1Char(';'), 1, 1)); } } if (KdenliveSettings::decklink_parameters().isEmpty() || KdenliveSettings::decklink_extension().isEmpty()) { KConfigGroup group(&conf, "decklink"); QMap values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString decklinkstring = i.value(); KdenliveSettings::setDecklink_parameters(decklinkstring.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setDecklink_extension(decklinkstring.section(QLatin1Char(';'), 1, 1)); } } if (!QDir(KdenliveSettings::currenttmpfolder()).isReadable()) KdenliveSettings::setCurrenttmpfolder(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QTimer::singleShot(0, this, &MainWindow::GUISetupDone); #ifdef USE_JOGSHUTTLE new JogManager(this); #endif scmanager->slotCheckActiveScopes(); // m_messageLabel->setMessage(QStringLiteral("This is a beta version. Always backup your data"), MltError); } void MainWindow::slotThemeChanged(const QString &name) { KSharedConfigPtr config = KSharedConfig::openConfig(name); QPalette plt = KColorScheme::createApplicationPalette(config); //qApp->setPalette(plt); // Required for qml palette change QGuiApplication::setPalette(plt); QColor background = plt.window().color(); bool useDarkIcons = background.value() < 100; if (m_assetPanel) { m_assetPanel->updatePalette(); } if (m_effectList2) { // Trigger a repaint to have icons adapted m_effectList2->reset(); } if (m_transitionList2) { // Trigger a repaint to have icons adapted m_transitionList2->reset(); } if (m_clipMonitor) { m_clipMonitor->setPalette(plt); } if (m_projectMonitor) { m_projectMonitor->setPalette(plt); } if (m_timelineTabs) { m_timelineTabs->setPalette(plt); getMainTimeline()->controller()->resetView(); } if (m_audioSpectrum) { m_audioSpectrum->refreshPixmap(); } KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup initialGroup(kconfig, "version"); if (initialGroup.exists() && KdenliveSettings::force_breeze() && useDarkIcons != KdenliveSettings::use_dark_breeze()) { // We need to reload icon theme QIcon::setThemeName(useDarkIcons ? QStringLiteral("breeze-dark") : QStringLiteral("breeze")); KdenliveSettings::setUse_dark_breeze(useDarkIcons); } #if KXMLGUI_VERSION_MINOR < 23 && KXMLGUI_VERSION_MAJOR == 5 // Not required anymore with auto colored icons since KF5 5.23 QColor background = plt.window().color(); bool useDarkIcons = background.value() < 100; if (m_themeInitialized && useDarkIcons != m_isDarkTheme) { if (pCore->bin()) { pCore->bin()->refreshIcons(); } if (m_clipMonitor) { m_clipMonitor->refreshIcons(); } if (m_projectMonitor) { m_projectMonitor->refreshIcons(); } if (pCore->monitorManager()) { pCore->monitorManager()->refreshIcons(); } for (QAction *action : actionCollection()->actions()) { QIcon icon = action->icon(); if (icon.isNull()) { continue; } QString iconName = icon.name(); QIcon newIcon = QIcon::fromTheme(iconName); if (newIcon.isNull()) { continue; } action->setIcon(newIcon); } } m_themeInitialized = true; m_isDarkTheme = useDarkIcons; #endif } void MainWindow::updateActionsToolTip() { // Add shortcut to action tooltips QList collections = KActionCollection::allCollections(); for (int i = 0; i < collections.count(); ++i) { KActionCollection *coll = collections.at(i); for (QAction *tempAction : coll->actions()) { // find the shortcut pattern and delete (note the preceding space in the RegEx) QString strippedTooltip = tempAction->toolTip().remove(QRegExp(QStringLiteral("\\s\\(.*\\)"))); // append shortcut if it exists for action if (tempAction->shortcut() == QKeySequence(0)) { tempAction->setToolTip(strippedTooltip); } else { tempAction->setToolTip(strippedTooltip + QStringLiteral(" (") + tempAction->shortcut().toString() + QLatin1Char(')')); } connect(tempAction, &QAction::changed, this, &MainWindow::updateAction); } } } void MainWindow::updateAction() { QAction *action = qobject_cast(sender()); QString toolTip = KLocalizedString::removeAcceleratorMarker(action->toolTip()); QString strippedTooltip = toolTip.remove(QRegExp(QStringLiteral("\\s\\(.*\\)"))); action->setToolTip(i18nc("@info:tooltip Tooltip of toolbar button", "%1 (%2)", strippedTooltip, action->shortcut().toString())); } MainWindow::~MainWindow() { pCore->prepareShutdown(); m_timelineTabs->disconnectTimeline(getMainTimeline()); delete m_audioSpectrum; if (m_projectMonitor) { m_projectMonitor->stop(); } if (m_clipMonitor) { m_clipMonitor->stop(); } ClipController::mediaUnavailable.reset(); delete m_projectMonitor; delete m_clipMonitor; delete m_shortcutRemoveFocus; delete m_effectList2; delete m_transitionList2; qDeleteAll(m_transitions); // Mlt::Factory::close(); } // virtual bool MainWindow::queryClose() { if (m_renderWidget) { int waitingJobs = m_renderWidget->waitingJobsCount(); if (waitingJobs > 0) { switch ( KMessageBox::warningYesNoCancel(this, i18np("You have 1 rendering job waiting in the queue.\nWhat do you want to do with this job?", "You have %1 rendering jobs waiting in the queue.\nWhat do you want to do with these jobs?", waitingJobs), QString(), KGuiItem(i18n("Start them now")), KGuiItem(i18n("Delete them")))) { case KMessageBox::Yes: // create script with waiting jobs and start it if (!m_renderWidget->startWaitingRenderJobs()) { return false; } break; case KMessageBox::No: // Don't do anything, jobs will be deleted break; default: return false; } } } saveOptions(); // WARNING: According to KMainWindow::queryClose documentation we are not supposed to close the document here? return pCore->projectManager()->closeCurrentDocument(true, true); } void MainWindow::loadGenerators() { QMenu *addMenu = static_cast(factory()->container(QStringLiteral("generators"), this)); Generators::getGenerators(KdenliveSettings::producerslist(), addMenu); connect(addMenu, &QMenu::triggered, this, &MainWindow::buildGenerator); } void MainWindow::buildGenerator(QAction *action) { Generators gen(m_clipMonitor, action->data().toString(), this); if (gen.exec() == QDialog::Accepted) { pCore->bin()->slotAddClipToProject(gen.getSavedClip()); } } void MainWindow::saveProperties(KConfigGroup &config) { // save properties here KXmlGuiWindow::saveProperties(config); // TODO: fix session management if (qApp->isSavingSession() && pCore->projectManager()) { if (pCore->currentDoc() && !pCore->currentDoc()->url().isEmpty()) { config.writeEntry("kdenlive_lastUrl", pCore->currentDoc()->url().toLocalFile()); } } } void MainWindow::readProperties(const KConfigGroup &config) { // read properties here KXmlGuiWindow::readProperties(config); // TODO: fix session management /*if (qApp->isSessionRestored()) { pCore->projectManager()->openFile(QUrl::fromLocalFile(config.readEntry("kdenlive_lastUrl", QString()))); }*/ } void MainWindow::saveNewToolbarConfig() { KXmlGuiWindow::saveNewToolbarConfig(); // TODO for some reason all dynamically inserted actions are removed by the save toolbar // So we currently re-add them manually.... loadDockActions(); loadClipActions(); pCore->bin()->rebuildMenu(); QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this)); if (monitorOverlay) { m_projectMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, nullptr, m_loopClip); m_clipMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, static_cast(factory()->container(QStringLiteral("marker_menu"), this))); } } void MainWindow::slotReloadEffects(const QStringList &paths) { for (const QString p : paths) { EffectsRepository::get()->reloadCustom(p); } m_effectList2->reloadEffectMenu(m_effectsMenu, m_effectActions); } void MainWindow::configureNotifications() { KNotifyConfigWidget::configure(this); } void MainWindow::slotFullScreen() { KToggleFullScreenAction::setFullScreen(this, actionCollection()->action(QStringLiteral("fullscreen"))->isChecked()); } void MainWindow::slotConnectMonitors() { // connect(m_projectList, SIGNAL(deleteProjectClips(QStringList,QMap)), this, // SLOT(slotDeleteProjectClips(QStringList,QMap))); connect(m_clipMonitor, &Monitor::refreshClipThumbnail, pCore->bin(), &Bin::slotRefreshClipThumbnail); connect(m_projectMonitor, &Monitor::requestFrameForAnalysis, this, &MainWindow::slotMonitorRequestRenderFrame); connect(m_projectMonitor, &Monitor::createSplitOverlay, this, &MainWindow::createSplitOverlay, Qt::DirectConnection); connect(m_projectMonitor, &Monitor::removeSplitOverlay, this, &MainWindow::removeSplitOverlay, Qt::DirectConnection); } void MainWindow::createSplitOverlay(Mlt::Filter *filter) { getMainTimeline()->controller()->createSplitOverlay(filter); m_projectMonitor->activateSplit(); } void MainWindow::removeSplitOverlay() { getMainTimeline()->controller()->removeSplitOverlay(); } void MainWindow::addAction(const QString &name, QAction *action, const QKeySequence &shortcut, KActionCategory *category) { m_actionNames.append(name); if (category) { category->addAction(name, action); } else { actionCollection()->addAction(name, action); } actionCollection()->setDefaultShortcut(action, shortcut); } QAction *MainWindow::addAction(const QString &name, const QString &text, const QObject *receiver, const char *member, const QIcon &icon, const QKeySequence &shortcut, KActionCategory *category) { auto *action = new QAction(text, this); if (!icon.isNull()) { action->setIcon(icon); } addAction(name, action, shortcut, category); connect(action, SIGNAL(triggered(bool)), receiver, member); return action; } void MainWindow::setupActions() { // create edit mode buttons m_normalEditTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-normal-edit")), i18n("Normal mode"), this); m_normalEditTool->setCheckable(true); m_normalEditTool->setChecked(true); m_overwriteEditTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-overwrite-edit")), i18n("Overwrite mode"), this); m_overwriteEditTool->setCheckable(true); m_overwriteEditTool->setChecked(false); m_insertEditTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-insert-edit")), i18n("Insert mode"), this); m_insertEditTool->setCheckable(true); m_insertEditTool->setChecked(false); KSelectAction *sceneMode = new KSelectAction(i18n("Timeline Edit Mode"), this); sceneMode->addAction(m_normalEditTool); sceneMode->addAction(m_overwriteEditTool); sceneMode->addAction(m_insertEditTool); sceneMode->setCurrentItem(0); connect(sceneMode, static_cast(&KSelectAction::triggered), this, &MainWindow::slotChangeEdit); addAction(QStringLiteral("timeline_mode"), sceneMode); m_useTimelineZone = new KDualAction(i18n("Don't Use Timeline Zone for Insert"), i18n("Use Timeline Zone for Insert"), this); m_useTimelineZone->setActiveIcon(QIcon::fromTheme(QStringLiteral("timeline-use-zone-on"))); m_useTimelineZone->setInactiveIcon(QIcon::fromTheme(QStringLiteral("timeline-use-zone-off"))); m_useTimelineZone->setAutoToggle(true); connect(m_useTimelineZone, &KDualAction::activeChangedByUser, this, &MainWindow::slotSwitchTimelineZone); addAction(QStringLiteral("use_timeline_zone_in_edit"), m_useTimelineZone, Qt::Key_G); m_compositeAction = new KSelectAction(QIcon::fromTheme(QStringLiteral("composite-track-off")), i18n("Track compositing"), this); m_compositeAction->setToolTip(i18n("Track compositing")); QAction *noComposite = new QAction(QIcon::fromTheme(QStringLiteral("composite-track-off")), i18n("None"), this); noComposite->setCheckable(true); noComposite->setData(0); m_compositeAction->addAction(noComposite); QString compose = TransitionsRepository::get()->getCompositingTransition(); if (compose == QStringLiteral("movit.overlay")) { // Movit, do not show "preview" option since movit is faster QAction *hqComposite = new QAction(QIcon::fromTheme(QStringLiteral("composite-track-on")), i18n("High Quality"), this); hqComposite->setCheckable(true); hqComposite->setData(2); m_compositeAction->addAction(hqComposite); m_compositeAction->setCurrentAction(hqComposite); } else { QAction *previewComposite = new QAction(QIcon::fromTheme(QStringLiteral("composite-track-preview")), i18n("Preview"), this); previewComposite->setCheckable(true); previewComposite->setData(1); m_compositeAction->addAction(previewComposite); if (compose != QStringLiteral("composite")) { QAction *hqComposite = new QAction(QIcon::fromTheme(QStringLiteral("composite-track-on")), i18n("High Quality"), this); hqComposite->setData(2); hqComposite->setCheckable(true); m_compositeAction->addAction(hqComposite); m_compositeAction->setCurrentAction(hqComposite); } else { m_compositeAction->setCurrentAction(previewComposite); } } connect(m_compositeAction, static_cast(&KSelectAction::triggered), this, &MainWindow::slotUpdateCompositing); addAction(QStringLiteral("timeline_compositing"), m_compositeAction); QAction *splitView = new QAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18n("Split Audio Tracks"), this); addAction(QStringLiteral("timeline_view_split"), splitView); splitView->setData(QVariant::fromValue(1)); splitView->setCheckable(true); splitView->setChecked(KdenliveSettings::audiotracksbelow()); QAction *mixedView = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Mixed Audio tracks"), this); addAction(QStringLiteral("timeline_mixed_view"), mixedView); mixedView->setData(QVariant::fromValue(0)); mixedView->setCheckable(true); mixedView->setChecked(!KdenliveSettings::audiotracksbelow()); QActionGroup *clipTypeGroup = new QActionGroup(this); clipTypeGroup->addAction(mixedView); clipTypeGroup->addAction(splitView); connect(clipTypeGroup, &QActionGroup::triggered, this, &MainWindow::slotUpdateTimelineView); auto tlsettings = new QMenu(this); tlsettings->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); tlsettings->addAction(m_compositeAction); tlsettings->addAction(mixedView); tlsettings->addAction(splitView); addAction(QStringLiteral("timeline_settings"), tlsettings->menuAction()); m_timeFormatButton = new KSelectAction(QStringLiteral("00:00:00:00 / 00:00:00:00"), this); m_timeFormatButton->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_timeFormatButton->addAction(i18n("hh:mm:ss:ff")); m_timeFormatButton->addAction(i18n("Frames")); if (KdenliveSettings::frametimecode()) { m_timeFormatButton->setCurrentItem(1); } else { m_timeFormatButton->setCurrentItem(0); } connect(m_timeFormatButton, static_cast(&KSelectAction::triggered), this, &MainWindow::slotUpdateTimecodeFormat); m_timeFormatButton->setToolBarMode(KSelectAction::MenuMode); m_timeFormatButton->setToolButtonPopupMode(QToolButton::InstantPopup); addAction(QStringLiteral("timeline_timecode"), m_timeFormatButton); // create tools buttons m_buttonSelectTool = new QAction(QIcon::fromTheme(QStringLiteral("cursor-arrow")), i18n("Selection tool"), this); // toolbar->addAction(m_buttonSelectTool); m_buttonSelectTool->setCheckable(true); m_buttonSelectTool->setChecked(true); m_buttonRazorTool = new QAction(QIcon::fromTheme(QStringLiteral("edit-cut")), i18n("Razor tool"), this); // toolbar->addAction(m_buttonRazorTool); m_buttonRazorTool->setCheckable(true); m_buttonRazorTool->setChecked(false); m_buttonSpacerTool = new QAction(QIcon::fromTheme(QStringLiteral("distribute-horizontal-x")), i18n("Spacer tool"), this); // toolbar->addAction(m_buttonSpacerTool); m_buttonSpacerTool->setCheckable(true); m_buttonSpacerTool->setChecked(false); auto *toolGroup = new QActionGroup(this); toolGroup->addAction(m_buttonSelectTool); toolGroup->addAction(m_buttonRazorTool); toolGroup->addAction(m_buttonSpacerTool); toolGroup->setExclusive(true); // toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); /*QWidget * actionWidget; int max = toolbar->iconSizeDefault() + 2; actionWidget = toolbar->widgetForAction(m_normalEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_insertEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_overwriteEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonSelectTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonRazorTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonSpacerTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4);*/ connect(toolGroup, &QActionGroup::triggered, this, &MainWindow::slotChangeTool); m_buttonVideoThumbs = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-videothumb")), i18n("Show video thumbnails"), this); m_buttonVideoThumbs->setCheckable(true); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); connect(m_buttonVideoThumbs, &QAction::triggered, this, &MainWindow::slotSwitchVideoThumbs); m_buttonAudioThumbs = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-audiothumb")), i18n("Show audio thumbnails"), this); m_buttonAudioThumbs->setCheckable(true); m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); connect(m_buttonAudioThumbs, &QAction::triggered, this, &MainWindow::slotSwitchAudioThumbs); m_buttonShowMarkers = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-markers")), i18n("Show markers comments"), this); m_buttonShowMarkers->setCheckable(true); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); connect(m_buttonShowMarkers, &QAction::triggered, this, &MainWindow::slotSwitchMarkersComments); m_buttonSnap = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-snap")), i18n("Snap"), this); m_buttonSnap->setCheckable(true); m_buttonSnap->setChecked(KdenliveSettings::snaptopoints()); connect(m_buttonSnap, &QAction::triggered, this, &MainWindow::slotSwitchSnap); m_buttonAutomaticTransition = new QAction(QIcon::fromTheme(QStringLiteral("auto-transition")), i18n("Automatic transitions"), this); m_buttonAutomaticTransition->setCheckable(true); m_buttonAutomaticTransition->setChecked(KdenliveSettings::automatictransitions()); connect(m_buttonAutomaticTransition, &QAction::triggered, this, &MainWindow::slotSwitchAutomaticTransition); m_buttonFitZoom = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-best")), i18n("Fit zoom to project"), this); m_buttonFitZoom->setCheckable(false); m_zoomSlider = new QSlider(Qt::Horizontal, this); m_zoomSlider->setRange(0, 20); m_zoomSlider->setPageStep(1); m_zoomSlider->setInvertedAppearance(true); m_zoomSlider->setInvertedControls(true); m_zoomSlider->setMaximumWidth(150); m_zoomSlider->setMinimumWidth(100); m_zoomIn = KStandardAction::zoomIn(this, SLOT(slotZoomIn()), actionCollection()); m_zoomOut = KStandardAction::zoomOut(this, SLOT(slotZoomOut()), actionCollection()); connect(m_zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(slotSetZoom(int))); connect(m_zoomSlider, &QAbstractSlider::sliderMoved, this, &MainWindow::slotShowZoomSliderToolTip); connect(m_buttonFitZoom, &QAction::triggered, this, &MainWindow::slotFitZoom); m_trimLabel = new QLabel(QStringLiteral(" "), this); m_trimLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); // m_trimLabel->setAutoFillBackground(true); m_trimLabel->setAlignment(Qt::AlignHCenter); m_trimLabel->setStyleSheet(QStringLiteral("QLabel { background-color :red; }")); KToolBar *toolbar = new KToolBar(QStringLiteral("statusToolBar"), this, Qt::BottomToolBarArea); toolbar->setMovable(false); toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); /*QString styleBorderless = QStringLiteral("QToolButton { border-width: 0px;margin: 1px 3px 0px;padding: 0px;}");*/ toolbar->addWidget(m_trimLabel); toolbar->addAction(m_buttonAutomaticTransition); toolbar->addAction(m_buttonVideoThumbs); toolbar->addAction(m_buttonAudioThumbs); toolbar->addAction(m_buttonShowMarkers); toolbar->addAction(m_buttonSnap); toolbar->addSeparator(); toolbar->addAction(m_buttonFitZoom); toolbar->addAction(m_zoomOut); toolbar->addWidget(m_zoomSlider); toolbar->addAction(m_zoomIn); int small = style()->pixelMetric(QStyle::PM_SmallIconSize); statusBar()->setMaximumHeight(2 * small); m_messageLabel = new StatusBarMessageLabel(this); m_messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); connect(this, &MainWindow::displayMessage, m_messageLabel, &StatusBarMessageLabel::setMessage); statusBar()->addWidget(m_messageLabel, 0); QWidget *spacer = new QWidget(this); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); statusBar()->addWidget(spacer, 1); statusBar()->addPermanentWidget(toolbar); toolbar->setIconSize(QSize(small, small)); toolbar->layout()->setContentsMargins(0, 0, 0, 0); statusBar()->setContentsMargins(0, 0, 0, 0); addAction(QStringLiteral("normal_mode"), m_normalEditTool); addAction(QStringLiteral("overwrite_mode"), m_overwriteEditTool); addAction(QStringLiteral("insert_mode"), m_insertEditTool); addAction(QStringLiteral("select_tool"), m_buttonSelectTool, Qt::Key_S); addAction(QStringLiteral("razor_tool"), m_buttonRazorTool, Qt::Key_X); addAction(QStringLiteral("spacer_tool"), m_buttonSpacerTool, Qt::Key_M); addAction(QStringLiteral("automatic_transition"), m_buttonAutomaticTransition); addAction(QStringLiteral("show_video_thumbs"), m_buttonVideoThumbs); addAction(QStringLiteral("show_audio_thumbs"), m_buttonAudioThumbs); addAction(QStringLiteral("show_markers"), m_buttonShowMarkers); addAction(QStringLiteral("snap"), m_buttonSnap); addAction(QStringLiteral("zoom_fit"), m_buttonFitZoom); addAction(QStringLiteral("run_wizard"), i18n("Run Config Wizard"), this, SLOT(slotRunWizard()), QIcon::fromTheme(QStringLiteral("tools-wizard"))); addAction(QStringLiteral("project_settings"), i18n("Project Settings"), this, SLOT(slotEditProjectSettings()), QIcon::fromTheme(QStringLiteral("configure"))); addAction(QStringLiteral("project_render"), i18n("Render"), this, SLOT(slotRenderProject()), QIcon::fromTheme(QStringLiteral("media-record")), Qt::CTRL + Qt::Key_Return); addAction(QStringLiteral("stop_project_render"), i18n("Stop Render"), this, SLOT(slotStopRenderProject()), QIcon::fromTheme(QStringLiteral("media-record"))); addAction(QStringLiteral("project_clean"), i18n("Clean Project"), this, SLOT(slotCleanProject()), QIcon::fromTheme(QStringLiteral("edit-clear"))); - // TODO - // addAction("project_adjust_profile", i18n("Adjust Profile to Current Clip"), pCore->bin(), SLOT(adjustProjectProfileToItem())); + addAction("project_adjust_profile", i18n("Adjust Profile to Current Clip"), pCore->bin(), SLOT(adjustProjectProfileToItem())); m_playZone = addAction(QStringLiteral("monitor_play_zone"), i18n("Play Zone"), pCore->monitorManager(), SLOT(slotPlayZone()), QIcon::fromTheme(QStringLiteral("media-playback-start")), Qt::CTRL + Qt::Key_Space); m_loopZone = addAction(QStringLiteral("monitor_loop_zone"), i18n("Loop Zone"), pCore->monitorManager(), SLOT(slotLoopZone()), QIcon::fromTheme(QStringLiteral("media-playback-start")), Qt::ALT + Qt::Key_Space); m_loopClip = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-start")), i18n("Loop selected clip"), this); addAction(QStringLiteral("monitor_loop_clip"), m_loopClip); m_loopClip->setEnabled(false); addAction(QStringLiteral("dvd_wizard"), i18n("DVD Wizard"), this, SLOT(slotDvdWizard()), QIcon::fromTheme(QStringLiteral("media-optical"))); addAction(QStringLiteral("transcode_clip"), i18n("Transcode Clips"), this, SLOT(slotTranscodeClip()), QIcon::fromTheme(QStringLiteral("edit-copy"))); addAction(QStringLiteral("archive_project"), i18n("Archive Project"), this, SLOT(slotArchiveProject()), QIcon::fromTheme(QStringLiteral("document-save-all"))); addAction(QStringLiteral("switch_monitor"), i18n("Switch monitor"), this, SLOT(slotSwitchMonitors()), QIcon(), Qt::Key_T); addAction(QStringLiteral("expand_timeline_clip"), i18n("Expand Clip"), pCore->projectManager(), SLOT(slotExpandClip()), QIcon::fromTheme(QStringLiteral("document-open"))); QAction *overlayInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Info Overlay"), this); addAction(QStringLiteral("monitor_overlay"), overlayInfo); overlayInfo->setCheckable(true); overlayInfo->setData(0x01); QAction *overlayTCInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Timecode"), this); addAction(QStringLiteral("monitor_overlay_tc"), overlayTCInfo); overlayTCInfo->setCheckable(true); overlayTCInfo->setData(0x02); QAction *overlayFpsInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Playback Fps"), this); addAction(QStringLiteral("monitor_overlay_fps"), overlayFpsInfo); overlayFpsInfo->setCheckable(true); overlayFpsInfo->setData(0x20); QAction *overlayMarkerInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Markers"), this); addAction(QStringLiteral("monitor_overlay_markers"), overlayMarkerInfo); overlayMarkerInfo->setCheckable(true); overlayMarkerInfo->setData(0x04); QAction *overlayAudioInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Audio Waveform"), this); addAction(QStringLiteral("monitor_overlay_audiothumb"), overlayAudioInfo); overlayAudioInfo->setCheckable(true); overlayAudioInfo->setData(0x10); QAction *dropFrames = new QAction(QIcon(), i18n("Real Time (drop frames)"), this); dropFrames->setCheckable(true); dropFrames->setChecked(KdenliveSettings::monitor_dropframes()); addAction(QStringLiteral("mlt_realtime"), dropFrames); connect(dropFrames, &QAction::toggled, this, &MainWindow::slotSwitchDropFrames); KSelectAction *monitorGamma = new KSelectAction(i18n("Monitor Gamma"), this); monitorGamma->addAction(i18n("sRGB (computer)")); monitorGamma->addAction(i18n("Rec. 709 (TV)")); addAction(QStringLiteral("mlt_gamma"), monitorGamma); monitorGamma->setCurrentItem(KdenliveSettings::monitor_gamma()); connect(monitorGamma, static_cast(&KSelectAction::triggered), this, &MainWindow::slotSetMonitorGamma); addAction(QStringLiteral("switch_trim"), i18n("Trim Mode"), this, SLOT(slotSwitchTrimMode()), QIcon::fromTheme(QStringLiteral("cursor-arrow"))); // disable shortcut until fully working, Qt::CTRL + Qt::Key_T); addAction(QStringLiteral("insert_project_tree"), i18n("Insert Zone in Project Bin"), this, SLOT(slotInsertZoneToTree()), QIcon(), Qt::CTRL + Qt::Key_I); addAction(QStringLiteral("insert_timeline"), i18n("Insert Zone in Timeline"), this, SLOT(slotInsertZoneToTimeline()), QIcon(), Qt::SHIFT + Qt::CTRL + Qt::Key_I); addAction(QStringLiteral("monitor_seek_snap_backward"), i18n("Go to Previous Snap Point"), this, SLOT(slotSnapRewind()), QIcon::fromTheme(QStringLiteral("media-seek-backward")), Qt::ALT + Qt::Key_Left); addAction(QStringLiteral("seek_clip_start"), i18n("Go to Clip Start"), this, SLOT(slotClipStart()), QIcon::fromTheme(QStringLiteral("media-seek-backward")), Qt::Key_Home); addAction(QStringLiteral("seek_clip_end"), i18n("Go to Clip End"), this, SLOT(slotClipEnd()), QIcon::fromTheme(QStringLiteral("media-seek-forward")), Qt::Key_End); addAction(QStringLiteral("monitor_seek_snap_forward"), i18n("Go to Next Snap Point"), this, SLOT(slotSnapForward()), QIcon::fromTheme(QStringLiteral("media-seek-forward")), Qt::ALT + Qt::Key_Right); addAction(QStringLiteral("align_playhead"), i18n("Align Playhead to Mouse Position"), this, SLOT(slotAlignPlayheadToMousePos()), QIcon(), Qt::Key_P); addAction(QStringLiteral("grab_item"), i18n("Grab Current Item"), this, SLOT(slotGrabItem()), QIcon::fromTheme(QStringLiteral("transform-move")), Qt::SHIFT + Qt::Key_G); QAction *stickTransition = new QAction(i18n("Automatic Transition"), this); stickTransition->setData(QStringLiteral("auto")); stickTransition->setCheckable(true); stickTransition->setEnabled(false); addAction(QStringLiteral("auto_transition"), stickTransition); connect(stickTransition, &QAction::triggered, this, &MainWindow::slotAutoTransition); addAction(QStringLiteral("overwrite_to_in_point"), i18n("Overwrite Clip Zone in Timeline"), this, SLOT(slotInsertClipOverwrite()), QIcon::fromTheme(QStringLiteral("timeline-overwrite")), Qt::Key_B); addAction(QStringLiteral("insert_to_in_point"), i18n("Insert Clip Zone in Timeline"), this, SLOT(slotInsertClipInsert()), QIcon::fromTheme(QStringLiteral("timeline-insert")), Qt::Key_V); addAction(QStringLiteral("remove_extract"), i18n("Extract Timeline Zone"), this, SLOT(slotExtractZone()), QIcon::fromTheme(QStringLiteral("timeline-extract")), Qt::SHIFT + Qt::Key_X); addAction(QStringLiteral("remove_lift"), i18n("Lift Timeline Zone"), this, SLOT(slotLiftZone()), QIcon::fromTheme(QStringLiteral("timeline-lift")), Qt::Key_Z); addAction(QStringLiteral("set_render_timeline_zone"), i18n("Add Preview Zone"), this, SLOT(slotDefinePreviewRender()), QIcon::fromTheme(QStringLiteral("preview-add-zone"))); addAction(QStringLiteral("unset_render_timeline_zone"), i18n("Remove Preview Zone"), this, SLOT(slotRemovePreviewRender()), QIcon::fromTheme(QStringLiteral("preview-remove-zone"))); addAction(QStringLiteral("clear_render_timeline_zone"), i18n("Remove All Preview Zones"), this, SLOT(slotClearPreviewRender()), QIcon::fromTheme(QStringLiteral("preview-remove-all"))); addAction(QStringLiteral("prerender_timeline_zone"), i18n("Start Preview Render"), this, SLOT(slotPreviewRender()), QIcon::fromTheme(QStringLiteral("preview-render-on")), QKeySequence(Qt::SHIFT + Qt::Key_Return)); addAction(QStringLiteral("stop_prerender_timeline"), i18n("Stop Preview Render"), this, SLOT(slotStopPreviewRender()), QIcon::fromTheme(QStringLiteral("preview-render-off"))); addAction(QStringLiteral("select_timeline_clip"), i18n("Select Clip"), this, SLOT(slotSelectTimelineClip()), QIcon::fromTheme(QStringLiteral("edit-select")), Qt::Key_Plus); addAction(QStringLiteral("deselect_timeline_clip"), i18n("Deselect Clip"), this, SLOT(slotDeselectTimelineClip()), QIcon::fromTheme(QStringLiteral("edit-select")), Qt::Key_Minus); addAction(QStringLiteral("select_add_timeline_clip"), i18n("Add Clip To Selection"), this, SLOT(slotSelectAddTimelineClip()), QIcon::fromTheme(QStringLiteral("edit-select")), Qt::ALT + Qt::Key_Plus); addAction(QStringLiteral("select_timeline_transition"), i18n("Select Transition"), this, SLOT(slotSelectTimelineTransition()), QIcon::fromTheme(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Plus); addAction(QStringLiteral("deselect_timeline_transition"), i18n("Deselect Transition"), this, SLOT(slotDeselectTimelineTransition()), QIcon::fromTheme(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Minus); addAction(QStringLiteral("select_add_timeline_transition"), i18n("Add Transition To Selection"), this, SLOT(slotSelectAddTimelineTransition()), QIcon::fromTheme(QStringLiteral("edit-select")), Qt::ALT + Qt::SHIFT + Qt::Key_Plus); addAction(QStringLiteral("add_clip_marker"), i18n("Add Marker"), this, SLOT(slotAddClipMarker()), QIcon::fromTheme(QStringLiteral("bookmark-new"))); addAction(QStringLiteral("delete_clip_marker"), i18n("Delete Marker"), this, SLOT(slotDeleteClipMarker()), QIcon::fromTheme(QStringLiteral("edit-delete"))); addAction(QStringLiteral("delete_all_clip_markers"), i18n("Delete All Markers"), this, SLOT(slotDeleteAllClipMarkers()), QIcon::fromTheme(QStringLiteral("edit-delete"))); QAction *editClipMarker = addAction(QStringLiteral("edit_clip_marker"), i18n("Edit Marker"), this, SLOT(slotEditClipMarker()), QIcon::fromTheme(QStringLiteral("document-properties"))); editClipMarker->setData(QStringLiteral("edit_marker")); addAction(QStringLiteral("add_marker_guide_quickly"), i18n("Add Marker/Guide quickly"), this, SLOT(slotAddMarkerGuideQuickly()), QIcon::fromTheme(QStringLiteral("bookmark-new")), Qt::Key_Asterisk); // Clip actions. We set some category info on the action data to enable/disable it contextually in timelinecontroller KActionCategory *clipActionCategory = new KActionCategory(i18n("Current Selection"), actionCollection()); QAction *splitAudio = addAction(QStringLiteral("clip_split"), i18n("Split Audio"), this, SLOT(slotSplitAV()), QIcon::fromTheme(QStringLiteral("document-new")), QKeySequence(), clipActionCategory); // "S" will be handled specifically to change the action name depending on current selection splitAudio->setData('S'); splitAudio->setEnabled(false); QAction *setAudioAlignReference = addAction(QStringLiteral("set_audio_align_ref"), i18n("Set Audio Reference"), this, SLOT(slotSetAudioAlignReference()), QIcon(), QKeySequence(), clipActionCategory); // "A" as data means this action should only be available for clips with audio setAudioAlignReference->setData('A'); setAudioAlignReference->setEnabled(false); QAction *alignAudio = addAction(QStringLiteral("align_audio"), i18n("Align Audio to Reference"), this, SLOT(slotAlignAudio()), QIcon(), QKeySequence(), clipActionCategory); // "A" as data means this action should only be available for clips with audio alignAudio->setData('A'); alignAudio->setEnabled(false); QAction *act = addAction(QStringLiteral("edit_item_duration"), i18n("Edit Duration"), this, SLOT(slotEditItemDuration()), QIcon::fromTheme(QStringLiteral("measure")), QKeySequence(), clipActionCategory); act->setEnabled(false); act = addAction(QStringLiteral("clip_in_project_tree"), i18n("Clip in Project Bin"), this, SLOT(slotClipInProjectTree()), QIcon::fromTheme(QStringLiteral("go-jump-definition")), QKeySequence(), clipActionCategory); act->setEnabled(false); // "C" as data means this action should only be available for clips - not for compositions act->setData('C'); act = addAction(QStringLiteral("cut_timeline_clip"), i18n("Cut Clip"), this, SLOT(slotCutTimelineClip()), QIcon::fromTheme(QStringLiteral("edit-cut")), Qt::SHIFT + Qt::Key_R, clipActionCategory); act->setEnabled(false); act = addAction(QStringLiteral("delete_timeline_clip"), i18n("Delete Selected Item"), this, SLOT(slotDeleteItem()), QIcon::fromTheme(QStringLiteral("edit-delete")), Qt::Key_Delete, clipActionCategory); QAction *resizeStart = new QAction(QIcon(), i18n("Resize Item Start"), this); addAction(QStringLiteral("resize_timeline_clip_start"), resizeStart, Qt::Key_1, clipActionCategory); resizeStart->setEnabled(false); connect(resizeStart, &QAction::triggered, this, &MainWindow::slotResizeItemStart); QAction *resizeEnd = new QAction(QIcon(), i18n("Resize Item End"), this); addAction(QStringLiteral("resize_timeline_clip_end"), resizeEnd, Qt::Key_2, clipActionCategory); resizeEnd->setEnabled(false); connect(resizeEnd, &QAction::triggered, this, &MainWindow::slotResizeItemEnd); QAction *pasteEffects = addAction(QStringLiteral("paste_effects"), i18n("Paste Effects"), this, SLOT(slotPasteEffects()), QIcon::fromTheme(QStringLiteral("edit-paste")), QKeySequence(), clipActionCategory); pasteEffects->setEnabled(false); // "C" as data means this action should only be available for clips - not for compositions pasteEffects->setData('C'); QAction *groupClip = addAction(QStringLiteral("group_clip"), i18n("Group Clips"), this, SLOT(slotGroupClips()), QIcon::fromTheme(QStringLiteral("object-group")), Qt::CTRL + Qt::Key_G, clipActionCategory); // "G" as data means this action should only be available for multiple items selection groupClip->setData('G'); groupClip->setEnabled(false); QAction *ungroupClip = addAction(QStringLiteral("ungroup_clip"), i18n("Ungroup Clips"), this, SLOT(slotUnGroupClips()), QIcon::fromTheme(QStringLiteral("object-ungroup")), Qt::CTRL + Qt::SHIFT + Qt::Key_G, clipActionCategory); // "U" as data means this action should only be available if selection is a group ungroupClip->setData('U'); ungroupClip->setEnabled(false); act = clipActionCategory->addAction(KStandardAction::Copy, this, SLOT(slotCopy())); act->setEnabled(false); KStandardAction::paste(this, SLOT(slotPaste()), actionCollection()); /*act = KStandardAction::copy(this, SLOT(slotCopy()), actionCollection()); clipActionCategory->addAction(KStandardAction::name(KStandardAction::Copy), act); act->setEnabled(false); act = KStandardAction::paste(this, SLOT(slotPaste()), actionCollection()); clipActionCategory->addAction(KStandardAction::name(KStandardAction::Paste), act); act->setEnabled(false);*/ kdenliveCategoryMap.insert(QStringLiteral("timelineselection"), clipActionCategory); addAction(QStringLiteral("insert_space"), i18n("Insert Space"), this, SLOT(slotInsertSpace())); addAction(QStringLiteral("delete_space"), i18n("Remove Space"), this, SLOT(slotRemoveSpace())); addAction(QStringLiteral("delete_space_all_tracks"), i18n("Remove Space In All Tracks"), this, SLOT(slotRemoveAllSpace())); KActionCategory *timelineActions = new KActionCategory(i18n("Tracks"), actionCollection()); QAction *insertTrack = new QAction(QIcon(), i18n("Insert Track"), this); connect(insertTrack, &QAction::triggered, this, &MainWindow::slotInsertTrack); timelineActions->addAction(QStringLiteral("insert_track"), insertTrack); QAction *deleteTrack = new QAction(QIcon(), i18n("Delete Track"), this); connect(deleteTrack, &QAction::triggered, this, &MainWindow::slotDeleteTrack); timelineActions->addAction(QStringLiteral("delete_track"), deleteTrack); deleteTrack->setData("delete_track"); QAction *selectTrack = new QAction(QIcon(), i18n("Select All in Current Track"), this); connect(selectTrack, &QAction::triggered, this, &MainWindow::slotSelectTrack); timelineActions->addAction(QStringLiteral("select_track"), selectTrack); QAction *selectAll = KStandardAction::selectAll(this, SLOT(slotSelectAllTracks()), this); selectAll->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-select-all"))); selectAll->setShortcutContext(Qt::WidgetWithChildrenShortcut); timelineActions->addAction(QStringLiteral("select_all_tracks"), selectAll); QAction *unselectAll = KStandardAction::deselect(this, SLOT(slotUnselectAllTracks()), this); unselectAll->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-unselect-all"))); unselectAll->setShortcutContext(Qt::WidgetWithChildrenShortcut); timelineActions->addAction(QStringLiteral("unselect_all_tracks"), unselectAll); kdenliveCategoryMap.insert(QStringLiteral("timeline"), timelineActions); // Cached data management addAction(QStringLiteral("manage_cache"), i18n("Manage Cached Data"), this, SLOT(slotManageCache()), QIcon::fromTheme(QStringLiteral("network-server-database"))); QAction *disablePreview = new QAction(i18n("Disable Timeline Preview"), this); disablePreview->setCheckable(true); addAction(QStringLiteral("disable_preview"), disablePreview); addAction(QStringLiteral("add_guide"), i18n("Add Guide"), this, SLOT(slotAddGuide()), QIcon::fromTheme(QStringLiteral("list-add"))); addAction(QStringLiteral("delete_guide"), i18n("Delete Guide"), this, SLOT(slotDeleteGuide()), QIcon::fromTheme(QStringLiteral("edit-delete"))); addAction(QStringLiteral("edit_guide"), i18n("Edit Guide"), this, SLOT(slotEditGuide()), QIcon::fromTheme(QStringLiteral("document-properties"))); addAction(QStringLiteral("delete_all_guides"), i18n("Delete All Guides"), this, SLOT(slotDeleteAllGuides()), QIcon::fromTheme(QStringLiteral("edit-delete"))); m_saveAction = KStandardAction::save(pCore->projectManager(), SLOT(saveFile()), actionCollection()); m_saveAction->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); addAction(QStringLiteral("save_selection"), i18n("Save Selection"), pCore->projectManager(), SLOT(slotSaveSelection()), QIcon::fromTheme(QStringLiteral("document-save"))); QAction *sentToLibrary = addAction(QStringLiteral("send_library"), i18n("Add Timeline Selection to Library"), pCore->library(), SLOT(slotAddToLibrary()), QIcon::fromTheme(QStringLiteral("bookmark-new"))); sentToLibrary->setEnabled(false); pCore->library()->setupActions(QList() << sentToLibrary); KStandardAction::showMenubar(this, SLOT(showMenuBar(bool)), actionCollection()); act = KStandardAction::quit(this, SLOT(close()), actionCollection()); //act->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); KStandardAction::keyBindings(this, SLOT(slotEditKeys()), actionCollection()); KStandardAction::preferences(this, SLOT(slotPreferences()), actionCollection()); KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection()); KStandardAction::fullScreen(this, SLOT(slotFullScreen()), this, actionCollection()); QAction *undo = KStandardAction::undo(m_commandStack, SLOT(undo()), actionCollection()); undo->setEnabled(false); connect(m_commandStack, &QUndoGroup::canUndoChanged, undo, &QAction::setEnabled); QAction *redo = KStandardAction::redo(m_commandStack, SLOT(redo()), actionCollection()); redo->setEnabled(false); connect(m_commandStack, &QUndoGroup::canRedoChanged, redo, &QAction::setEnabled); auto *addClips = new QMenu(this); QAction *addClip = addAction(QStringLiteral("add_clip"), i18n("Add Clip"), pCore->bin(), SLOT(slotAddClip()), QIcon::fromTheme(QStringLiteral("kdenlive-add-clip"))); addClips->addAction(addClip); QAction *action = addAction(QStringLiteral("add_color_clip"), i18n("Add Color Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), QIcon::fromTheme(QStringLiteral("kdenlive-add-color-clip"))); action->setData((int)ClipType::Color); addClips->addAction(action); action = addAction(QStringLiteral("add_slide_clip"), i18n("Add Slideshow Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), QIcon::fromTheme(QStringLiteral("kdenlive-add-slide-clip"))); action->setData((int)ClipType::SlideShow); addClips->addAction(action); action = addAction(QStringLiteral("add_text_clip"), i18n("Add Title Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), QIcon::fromTheme(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int)ClipType::Text); addClips->addAction(action); action = addAction(QStringLiteral("add_text_template_clip"), i18n("Add Template Title"), pCore->bin(), SLOT(slotCreateProjectClip()), QIcon::fromTheme(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int)ClipType::TextTemplate); addClips->addAction(action); /*action = addAction(QStringLiteral("add_qtext_clip"), i18n("Add Simple Text Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), QIcon::fromTheme(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int) QText); addClips->addAction(action);*/ QAction *addFolder = addAction(QStringLiteral("add_folder"), i18n("Create Folder"), pCore->bin(), SLOT(slotAddFolder()), QIcon::fromTheme(QStringLiteral("folder-new"))); addClips->addAction(addAction(QStringLiteral("download_resource"), i18n("Online Resources"), this, SLOT(slotDownloadResources()), QIcon::fromTheme(QStringLiteral("edit-download")))); QAction *clipProperties = addAction(QStringLiteral("clip_properties"), i18n("Clip Properties"), pCore->bin(), SLOT(slotSwitchClipProperties()), QIcon::fromTheme(QStringLiteral("document-edit"))); clipProperties->setData("clip_properties"); QAction *openClip = addAction(QStringLiteral("edit_clip"), i18n("Edit Clip"), pCore->bin(), SLOT(slotOpenClip()), QIcon::fromTheme(QStringLiteral("document-open"))); openClip->setData("edit_clip"); openClip->setEnabled(false); QAction *deleteClip = addAction(QStringLiteral("delete_clip"), i18n("Delete Clip"), pCore->bin(), SLOT(slotDeleteClip()), QIcon::fromTheme(QStringLiteral("edit-delete"))); deleteClip->setData("delete_clip"); deleteClip->setEnabled(false); QAction *reloadClip = addAction(QStringLiteral("reload_clip"), i18n("Reload Clip"), pCore->bin(), SLOT(slotReloadClip()), QIcon::fromTheme(QStringLiteral("view-refresh"))); reloadClip->setData("reload_clip"); reloadClip->setEnabled(false); QAction *disableEffects = addAction(QStringLiteral("disable_timeline_effects"), i18n("Disable Timeline Effects"), pCore->projectManager(), SLOT(slotDisableTimelineEffects(bool)), QIcon::fromTheme(QStringLiteral("favorite"))); disableEffects->setData("disable_timeline_effects"); disableEffects->setCheckable(true); disableEffects->setChecked(false); QAction *locateClip = addAction(QStringLiteral("locate_clip"), i18n("Locate Clip..."), pCore->bin(), SLOT(slotLocateClip()), QIcon::fromTheme(QStringLiteral("edit-file"))); locateClip->setData("locate_clip"); locateClip->setEnabled(false); QAction *duplicateClip = addAction(QStringLiteral("duplicate_clip"), i18n("Duplicate Clip"), pCore->bin(), SLOT(slotDuplicateClip()), QIcon::fromTheme(QStringLiteral("edit-copy"))); duplicateClip->setData("duplicate_clip"); duplicateClip->setEnabled(false); QAction *proxyClip = new QAction(i18n("Proxy Clip"), this); addAction(QStringLiteral("proxy_clip"), proxyClip); proxyClip->setData(QStringList() << QString::number((int)AbstractClipJob::PROXYJOB)); proxyClip->setCheckable(true); proxyClip->setChecked(false); addAction(QStringLiteral("switch_track_lock"), i18n("Toggle Track Lock"), pCore->projectManager(), SLOT(slotSwitchTrackLock()), QIcon(), Qt::SHIFT + Qt::Key_L); addAction(QStringLiteral("switch_all_track_lock"), i18n("Toggle All Track Lock"), pCore->projectManager(), SLOT(slotSwitchAllTrackLock()), QIcon(), Qt::CTRL + Qt::SHIFT + Qt::Key_L); addAction(QStringLiteral("switch_track_target"), i18n("Toggle Track Target"), pCore->projectManager(), SLOT(slotSwitchTrackTarget()), QIcon(), Qt::SHIFT + Qt::Key_T); addAction(QStringLiteral("add_project_note"), i18n("Add Project Note"), pCore->projectManager(), SLOT(slotAddProjectNote()), QIcon::fromTheme(QStringLiteral("bookmark"))); QHash actions; actions.insert(QStringLiteral("locate"), locateClip); actions.insert(QStringLiteral("reload"), reloadClip); actions.insert(QStringLiteral("duplicate"), duplicateClip); actions.insert(QStringLiteral("proxy"), proxyClip); actions.insert(QStringLiteral("properties"), clipProperties); actions.insert(QStringLiteral("open"), openClip); actions.insert(QStringLiteral("delete"), deleteClip); actions.insert(QStringLiteral("folder"), addFolder); pCore->bin()->setupMenu(addClips, addClip, actions); // Setup effects and transitions actions. KActionCategory *transitionActions = new KActionCategory(i18n("Transitions"), actionCollection()); // m_transitions = new QAction*[transitions.count()]; auto allTransitions = TransitionsRepository::get()->getNames(); for (const auto &transition : allTransitions) { auto *transAction = new QAction(transition.first, this); transAction->setData(transition.second); transAction->setIconVisibleInMenu(false); transitionActions->addAction("transition_" + transition.second, transAction); } // monitor actions addAction(QStringLiteral("extract_frame"), i18n("Extract frame..."), pCore->monitorManager(), SLOT(slotExtractCurrentFrame()), QIcon::fromTheme(QStringLiteral("insert-image"))); addAction(QStringLiteral("extract_frame_to_project"), i18n("Extract frame to project..."), pCore->monitorManager(), SLOT(slotExtractCurrentFrameToProject()), QIcon::fromTheme(QStringLiteral("insert-image"))); } void MainWindow::saveOptions() { KdenliveSettings::self()->save(); } bool MainWindow::readOptions() { KSharedConfigPtr config = KSharedConfig::openConfig(); pCore->projectManager()->recentFilesAction()->loadEntries(KConfigGroup(config, "Recent Files")); if (KdenliveSettings::defaultprojectfolder().isEmpty()) { QDir dir(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)); dir.mkpath(QStringLiteral(".")); KdenliveSettings::setDefaultprojectfolder(dir.absolutePath()); } if (KdenliveSettings::trackheight() == 0) { QFontMetrics metrics(font()); int trackHeight = 2 * metrics.height(); QStyle *style = qApp->style(); trackHeight += style->pixelMetric(QStyle::PM_ToolBarIconSize) + 2 * style->pixelMetric(QStyle::PM_ToolBarItemMargin) + style->pixelMetric(QStyle::PM_ToolBarItemSpacing) + 2; KdenliveSettings::setTrackheight(trackHeight); } if (KdenliveSettings::trackheight() == 0) { KdenliveSettings::setTrackheight(50); } bool firstRun = false; KConfigGroup initialGroup(config, "version"); if (!initialGroup.exists() || KdenliveSettings::sdlAudioBackend().isEmpty()) { // First run, check if user is on a KDE Desktop firstRun = true; // this is our first run, show Wizard QPointer w = new Wizard(true, false); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); delete w; } else { delete w; ::exit(1); } } else if (!KdenliveSettings::ffmpegpath().isEmpty() && !QFile::exists(KdenliveSettings::ffmpegpath())) { // Invalid entry for FFmpeg, check system QPointer w = new Wizard(true, config->name().contains(QLatin1String("appimage"))); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); } delete w; } initialGroup.writeEntry("version", version); return firstRun; } void MainWindow::slotRunWizard() { QPointer w = new Wizard(false, false, this); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); } delete w; } void MainWindow::slotRefreshProfiles() { KdenliveSettingsDialog *d = static_cast(KConfigDialog::exists(QStringLiteral("settings"))); if (d) { d->checkProfile(); } } void MainWindow::slotEditProjectSettings() { KdenliveDoc *project = pCore->currentDoc(); QPoint p = getMainTimeline()->getTracksCount(); ProjectSettings *w = new ProjectSettings(project, project->metadata(), getMainTimeline()->controller()->extractCompositionLumas(), p.x(), p.y(), project->projectTempFolder(), true, !project->isModified(), this); connect(w, &ProjectSettings::disableProxies, this, &MainWindow::slotDisableProxies); // connect(w, SIGNAL(disablePreview()), pCore->projectManager()->currentTimeline(), SLOT(invalidateRange())); connect(w, &ProjectSettings::refreshProfiles, this, &MainWindow::slotRefreshProfiles); if (w->exec() == QDialog::Accepted) { QString profile = w->selectedProfile(); // project->setProjectFolder(w->selectedFolder()); bool modified = false; if (m_renderWidget) { m_renderWidget->updateDocumentPath(); } if (KdenliveSettings::videothumbnails() != w->enableVideoThumbs()) { slotSwitchVideoThumbs(); } if (KdenliveSettings::audiothumbnails() != w->enableAudioThumbs()) { slotSwitchAudioThumbs(); } if (pCore->getCurrentProfile()->path() != profile || project->profileChanged(profile)) { pCore->setCurrentProfile(profile); pCore->projectManager()->slotResetProfiles(); slotUpdateDocumentState(true); } if (project->getDocumentProperty(QStringLiteral("proxyparams")) != w->proxyParams() || project->getDocumentProperty(QStringLiteral("proxyextension")) != w->proxyExtension()) { modified = true; project->setDocumentProperty(QStringLiteral("proxyparams"), w->proxyParams()); project->setDocumentProperty(QStringLiteral("proxyextension"), w->proxyExtension()); if (pCore->projectItemModel()->clipsCount() > 0 && KMessageBox::questionYesNo(this, i18n("You have changed the proxy parameters. Do you want to recreate all proxy clips for this project?")) == KMessageBox::Yes) { pCore->bin()->rebuildProxies(); } } if (project->getDocumentProperty(QStringLiteral("generateproxy")) != QString::number((int)w->generateProxy())) { modified = true; project->setDocumentProperty(QStringLiteral("generateproxy"), QString::number((int)w->generateProxy())); } if (project->getDocumentProperty(QStringLiteral("proxyminsize")) != QString::number(w->proxyMinSize())) { modified = true; project->setDocumentProperty(QStringLiteral("proxyminsize"), QString::number(w->proxyMinSize())); } if (project->getDocumentProperty(QStringLiteral("generateimageproxy")) != QString::number((int)w->generateImageProxy())) { modified = true; project->setDocumentProperty(QStringLiteral("generateimageproxy"), QString::number((int)w->generateImageProxy())); } if (project->getDocumentProperty(QStringLiteral("proxyimageminsize")) != QString::number(w->proxyImageMinSize())) { modified = true; project->setDocumentProperty(QStringLiteral("proxyimageminsize"), QString::number(w->proxyImageMinSize())); } if (project->getDocumentProperty(QStringLiteral("proxyimagesize")) != QString::number(w->proxyImageSize())) { modified = true; project->setDocumentProperty(QStringLiteral("proxyimagesize"), QString::number(w->proxyImageSize())); } if (QString::number((int)w->useProxy()) != project->getDocumentProperty(QStringLiteral("enableproxy"))) { project->setDocumentProperty(QStringLiteral("enableproxy"), QString::number((int)w->useProxy())); modified = true; slotUpdateProxySettings(); } if (w->metadata() != project->metadata()) { project->setMetadata(w->metadata()); } QString newProjectFolder = w->storageFolder(); if (newProjectFolder.isEmpty()) { newProjectFolder = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); } if (newProjectFolder != project->projectTempFolder()) { KMessageBox::ButtonCode answer; // Project folder changed: if (project->isModified()) { answer = KMessageBox::warningContinueCancel(this, i18n("The current project has not been saved. This will first save the project, then move " "all temporary files from %1 to %2, and the project file will be reloaded", project->projectTempFolder(), newProjectFolder)); if (answer == KMessageBox::Continue) { pCore->projectManager()->saveFile(); } } else { answer = KMessageBox::warningContinueCancel( this, i18n("This will move all temporary files from %1 to %2, the project file will then be reloaded", project->projectTempFolder(), newProjectFolder)); } if (answer == KMessageBox::Continue) { // Proceeed with move QString documentId = QDir::cleanPath(project->getDocumentProperty(QStringLiteral("documentid"))); bool ok; documentId.toLongLong(&ok, 10); if (!ok || documentId.isEmpty()) { KMessageBox::sorry(this, i18n("Cannot perform operation, invalid document id: %1", documentId)); } else { QDir newDir(newProjectFolder); QDir oldDir(project->projectTempFolder()); if (newDir.exists(documentId)) { KMessageBox::sorry(this, i18n("Cannot perform operation, target directory already exists: %1", newDir.absoluteFilePath(documentId))); } else { // Proceed with the move pCore->projectManager()->moveProjectData(oldDir.absoluteFilePath(documentId), newDir.absolutePath()); } } } } if (modified) { project->setModified(); } } delete w; } void MainWindow::slotDisableProxies() { pCore->currentDoc()->setDocumentProperty(QStringLiteral("enableproxy"), QString::number((int)false)); pCore->currentDoc()->setModified(); slotUpdateProxySettings(); } void MainWindow::slotStopRenderProject() { if (m_renderWidget) { m_renderWidget->slotAbortCurrentJob(); } } void MainWindow::slotRenderProject() { KdenliveDoc *project = pCore->currentDoc(); if (!m_renderWidget) { QString projectfolder = project ? project->projectDataFolder() + QDir::separator() : KdenliveSettings::defaultprojectfolder(); if (project) { m_renderWidget = new RenderWidget(project->useProxy(), this); connect(m_renderWidget, &RenderWidget::shutdown, this, &MainWindow::slotShutdown); connect(m_renderWidget, &RenderWidget::selectedRenderProfile, this, &MainWindow::slotSetDocumentRenderProfile); connect(m_renderWidget, &RenderWidget::abortProcess, this, &MainWindow::abortRenderJob); connect(m_renderWidget, &RenderWidget::openDvdWizard, this, &MainWindow::slotDvdWizard); connect(this, &MainWindow::updateRenderWidgetProfile, m_renderWidget, &RenderWidget::adjustViewToProfile); double projectDuration = GenTime(getMainTimeline()->controller()->duration(), pCore->getCurrentFps()).ms() / 1000; m_renderWidget->setGuides(project->getGuideModel()->getAllMarkers(), projectDuration); m_renderWidget->updateDocumentPath(); m_renderWidget->setRenderProfile(project->getRenderProperties()); } if (m_compositeAction->currentAction()) { m_renderWidget->errorMessage(RenderWidget::CompositeError, m_compositeAction->currentAction()->data().toInt() == 1 ? i18n("Rendering using low quality track compositing") : QString()); } } slotCheckRenderStatus(); m_renderWidget->show(); // m_renderWidget->showNormal(); // What are the following lines supposed to do? // m_renderWidget->enableAudio(false); // m_renderWidget->export_audio; } void MainWindow::slotCheckRenderStatus() { // Make sure there are no missing clips // TODO /*if (m_renderWidget) m_renderWidget->missingClips(pCore->bin()->hasMissingClips());*/ } void MainWindow::setRenderingProgress(const QString &url, int progress) { emit setRenderProgress(progress); if (m_renderWidget) { m_renderWidget->setRenderJob(url, progress); } } void MainWindow::setRenderingFinished(const QString &url, int status, const QString &error) { emit setRenderProgress(100); if (m_renderWidget) { m_renderWidget->setRenderStatus(url, status, error); } } void MainWindow::addProjectClip(const QString &url) { if (pCore->currentDoc()) { QStringList ids = pCore->projectItemModel()->getClipByUrl(QFileInfo(url)); if (!ids.isEmpty()) { // Clip is already in project bin, abort return; } ClipCreator::createClipFromFile(url, pCore->projectItemModel()->getRootFolder()->clipId(), pCore->projectItemModel()); } } void MainWindow::addTimelineClip(const QString &url) { if (pCore->currentDoc()) { QStringList ids = pCore->projectItemModel()->getClipByUrl(QFileInfo(url)); if (!ids.isEmpty()) { pCore->selectBinClip(ids.constFirst()); slotInsertClipInsert(); } } } void MainWindow::scriptRender(const QString &url) { slotRenderProject(); m_renderWidget->slotPrepareExport(true, url); } void MainWindow::exitApp() { QApplication::exit(0); } void MainWindow::slotCleanProject() { if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) { return; } pCore->bin()->cleanup(); } void MainWindow::slotUpdateMousePosition(int pos) { if (pCore->currentDoc()) { switch (m_timeFormatButton->currentItem()) { case 0: m_timeFormatButton->setText(pCore->currentDoc()->timecode().getTimecodeFromFrames(pos) + QStringLiteral(" / ") + pCore->currentDoc()->timecode().getTimecodeFromFrames(getMainTimeline()->controller()->duration())); break; default: m_timeFormatButton->setText( QStringLiteral("%1 / %2").arg(pos, 6, 10, QLatin1Char('0')).arg(getMainTimeline()->controller()->duration(), 6, 10, QLatin1Char('0'))); } } } void MainWindow::slotUpdateProjectDuration(int pos) { Q_UNUSED(pos) if (pCore->currentDoc()) { slotUpdateMousePosition(getMainTimeline()->controller()->getMousePos()); } } void MainWindow::slotUpdateDocumentState(bool modified) { setWindowTitle(pCore->currentDoc()->description()); setWindowModified(modified); m_saveAction->setEnabled(modified); } void MainWindow::connectDocument() { KdenliveDoc *project = pCore->currentDoc(); connect(project, &KdenliveDoc::startAutoSave, pCore->projectManager(), &ProjectManager::slotStartAutoSave); connect(project, &KdenliveDoc::reloadEffects, this, &MainWindow::slotReloadEffects); KdenliveSettings::setProject_fps(pCore->getCurrentFps()); m_projectMonitor->slotLoadClipZone(project->zone()); connect(m_projectMonitor, &Monitor::multitrackView, getMainTimeline()->controller(), &TimelineController::slotMultitrackView, Qt::UniqueConnection); connect(getMainTimeline()->controller(), &TimelineController::timelineClipSelected, pCore->library(), &LibraryWidget::enableAddSelection, Qt::UniqueConnection); connect(pCore->library(), &LibraryWidget::saveTimelineSelection, getMainTimeline()->controller(), &TimelineController::saveTimelineSelection, Qt::UniqueConnection); // TODO REFAC: reconnect to new timeline /* Timeline *trackView = pCore->projectManager()->currentTimeline(); connect(trackView, &Timeline::configTrack, this, &MainWindow::slotConfigTrack); connect(trackView, &Timeline::updateTracksInfo, this, &MainWindow::slotUpdateTrackInfo); connect(trackView, &Timeline::mousePosition, this, &MainWindow::slotUpdateMousePosition); connect(pCore->producerQueue(), &ProducerQueue::infoProcessingFinished, trackView->projectView(), &CustomTrackView::slotInfoProcessingFinished, Qt::DirectConnection); connect(trackView->projectView(), &CustomTrackView::importKeyframes, this, &MainWindow::slotProcessImportKeyframes); connect(trackView->projectView(), &CustomTrackView::updateTrimMode, this, &MainWindow::setTrimMode); connect(m_projectMonitor, SIGNAL(renderPosition(int)), trackView, SLOT(moveCursorPos(int))); connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), trackView, SLOT(slotSetZone(QPoint))); connect(trackView->projectView(), &CustomTrackView::guidesUpdated, this, &MainWindow::slotGuidesUpdated); connect(trackView->projectView(), &CustomTrackView::loadMonitorScene, m_projectMonitor, &Monitor::slotShowEffectScene); connect(trackView->projectView(), &CustomTrackView::setQmlProperty, m_projectMonitor, &Monitor::setQmlProperty); connect(m_projectMonitor, SIGNAL(acceptRipple(bool)), trackView->projectView(), SLOT(slotAcceptRipple(bool))); connect(m_projectMonitor, SIGNAL(switchTrimMode(int)), trackView->projectView(), SLOT(switchTrimMode(int))); connect(project, &KdenliveDoc::saveTimelinePreview, trackView, &Timeline::slotSaveTimelinePreview); connect(trackView, SIGNAL(showTrackEffects(int, TrackInfo)), this, SLOT(slotTrackSelected(int, TrackInfo))); connect(trackView->projectView(), &CustomTrackView::clipItemSelected, this, &MainWindow::slotTimelineClipSelected, Qt::DirectConnection); connect(trackView->projectView(), &CustomTrackView::setActiveKeyframe, m_effectStack, &EffectStackView2::setActiveKeyframe); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition *, int, QPoint, bool)), m_effectStack, SLOT(slotTransitionItemSelected(Transition *, int, QPoint, bool)), Qt::DirectConnection); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition *, int, QPoint, bool)), this, SLOT(slotActivateTransitionView(Transition *))); connect(trackView->projectView(), &CustomTrackView::zoomIn, this, &MainWindow::slotZoomIn); connect(trackView->projectView(), &CustomTrackView::zoomOut, this, &MainWindow::slotZoomOut); connect(trackView, SIGNAL(setZoom(int)), this, SLOT(slotSetZoom(int))); connect(trackView, SIGNAL(displayMessage(QString, MessageType)), m_messageLabel, SLOT(setMessage(QString, MessageType))); connect(trackView->projectView(), SIGNAL(displayMessage(QString, MessageType)), m_messageLabel, SLOT(setMessage(QString, MessageType))); connect(pCore->bin(), &Bin::clipNameChanged, trackView->projectView(), &CustomTrackView::clipNameChanged); connect(trackView->projectView(), SIGNAL(showClipFrame(QString, int)), pCore->bin(), SLOT(selectClipById(QString, int))); connect(trackView->projectView(), SIGNAL(playMonitor()), m_projectMonitor, SLOT(slotPlay())); connect(trackView->projectView(), &CustomTrackView::pauseMonitor, m_projectMonitor, &Monitor::pause, Qt::DirectConnection); connect(m_projectMonitor, &Monitor::addEffect, trackView->projectView(), &CustomTrackView::slotAddEffectToCurrentItem); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition *, int, QPoint, bool)), m_projectMonitor, SLOT(slotSetSelectedClip(Transition *))); connect(pCore->bin(), SIGNAL(gotFilterJobResults(QString, int, int, stringMap, stringMap)), trackView->projectView(), SLOT(slotGotFilterJobResults(QString, int, int, stringMap, stringMap))); //TODO //connect(m_projectList, SIGNAL(addMarkers(QString,QList)), trackView->projectView(), SLOT(slotAddClipMarker(QString,QList))); // Effect stack signals connect(m_effectStack, &EffectStackView2::updateEffect, trackView->projectView(), &CustomTrackView::slotUpdateClipEffect); connect(m_effectStack, &EffectStackView2::updateClipRegion, trackView->projectView(), &CustomTrackView::slotUpdateClipRegion); connect(m_effectStack, SIGNAL(removeEffect(ClipItem *, int, QDomElement)), trackView->projectView(), SLOT(slotDeleteEffect(ClipItem *, int, QDomElement))); connect(m_effectStack, SIGNAL(removeEffectGroup(ClipItem *, int, QDomDocument)), trackView->projectView(), SLOT(slotDeleteEffectGroup(ClipItem *, int, QDomDocument))); connect(m_effectStack, SIGNAL(addEffect(ClipItem *, QDomElement, int)), trackView->projectView(), SLOT(slotAddEffect(ClipItem *, QDomElement, int))); connect(m_effectStack, SIGNAL(changeEffectState(ClipItem *, int, QList, bool)), trackView->projectView(), SLOT(slotChangeEffectState(ClipItem *, int, QList, bool))); connect(m_effectStack, SIGNAL(changeEffectPosition(ClipItem *, int, QList, int)), trackView->projectView(), SLOT(slotChangeEffectPosition(ClipItem *, int, QList, int))); connect(m_effectStack, &EffectStackView2::refreshEffectStack, trackView->projectView(), &CustomTrackView::slotRefreshEffects); connect(m_effectStack, &EffectStackView2::seekTimeline, trackView->projectView(), &CustomTrackView::seekCursorPos); connect(m_effectStack, SIGNAL(importClipKeyframes(GraphicsRectItem, ItemInfo, QDomElement, QMap)), trackView->projectView(), SLOT(slotImportClipKeyframes(GraphicsRectItem, ItemInfo, QDomElement, QMap))); // Transition config signals connect(m_effectStack->transitionConfig(), SIGNAL(transitionUpdated(Transition *, QDomElement)), trackView->projectView(), SLOT(slotTransitionUpdated(Transition *, QDomElement))); connect(m_effectStack->transitionConfig(), &TransitionSettings::seekTimeline, trackView->projectView(), &CustomTrackView::seekCursorPos); connect(trackView->projectView(), SIGNAL(activateDocumentMonitor()), m_projectMonitor, SLOT(slotActivateMonitor()), Qt::DirectConnection); connect(project, &KdenliveDoc::updateFps, this, [this](double changed) { if (changed == 0.0) { slotUpdateProfile(false); } else { slotUpdateProfile(true); } }, Qt::DirectConnection); connect(trackView, &Timeline::zoneMoved, this, &MainWindow::slotZoneMoved); trackView->projectView()->setContextMenu(m_timelineContextMenu, m_timelineClipActions, m_timelineContextTransitionMenu, m_clipTypeGroup, static_cast(factory()->container(QStringLiteral("marker_menu"), this))); */ getMainTimeline()->controller()->clipActions = kdenliveCategoryMap.value(QStringLiteral("timelineselection"))->actions(); connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified())); connect(m_clipMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified())); connect(project, &KdenliveDoc::docModified, this, &MainWindow::slotUpdateDocumentState); connect(pCore->bin(), SIGNAL(displayMessage(QString, int, MessageType)), m_messageLabel, SLOT(setProgressMessage(QString, int, MessageType))); if (m_renderWidget) { slotCheckRenderStatus(); // m_renderWidget->setGuides(pCore->projectManager()->currentTimeline()->projectView()->guidesData(), project->projectDuration()); m_renderWidget->updateDocumentPath(); m_renderWidget->setRenderProfile(project->getRenderProperties()); } m_zoomSlider->setValue(project->zoom().x()); m_commandStack->setActiveStack(project->commandStack().get()); setWindowTitle(project->description()); setWindowModified(project->isModified()); m_saveAction->setEnabled(project->isModified()); m_normalEditTool->setChecked(true); connect(m_projectMonitor, &Monitor::durationChanged, this, &MainWindow::slotUpdateProjectDuration); pCore->monitorManager()->setDocument(project); connect(m_effectList2, &EffectListWidget::reloadFavorites, getMainTimeline(), &TimelineWidget::updateEffectFavorites); connect(m_transitionList2, &TransitionListWidget::reloadFavorites, getMainTimeline(), &TimelineWidget::updateTransitionFavorites); // TODO REFAC: fix // trackView->updateProfile(1.0); // Init document zone // m_projectMonitor->slotZoneMoved(trackView->inPoint(), trackView->outPoint()); // Update the mouse position display so it will display in DF/NDF format by default based on the project setting. // slotUpdateMousePosition(0); // Update guides info in render widget // slotGuidesUpdated(); // set tool to select tool setTrimMode(QString()); m_buttonSelectTool->setChecked(true); connect(m_projectMonitorDock, &QDockWidget::visibilityChanged, m_projectMonitor, &Monitor::slotRefreshMonitor, Qt::UniqueConnection); connect(m_clipMonitorDock, &QDockWidget::visibilityChanged, m_clipMonitor, &Monitor::slotRefreshMonitor, Qt::UniqueConnection); } void MainWindow::slotZoneMoved(int start, int end) { pCore->currentDoc()->setZone(start, end); QPoint zone(start, end); m_projectMonitor->slotLoadClipZone(zone); } void MainWindow::slotGuidesUpdated() { if (m_renderWidget) { double projectDuration = GenTime(getMainTimeline()->controller()->duration() - TimelineModel::seekDuration - 2, pCore->getCurrentFps()).ms() / 1000; m_renderWidget->setGuides(pCore->currentDoc()->getGuideModel()->getAllMarkers(), projectDuration); } } void MainWindow::slotEditKeys() { KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); // Find the combobox inside KShortcutsDialog for choosing keyboard scheme QComboBox *schemesList = nullptr; foreach (QLabel *label, dialog.findChildren()) { if (label->text() == i18n("Current scheme:")) { schemesList = qobject_cast(label->buddy()); break; } } // If scheme choosing combobox was found, find the "More Actions" button in the same // dialog that provides a dropdown menu with additional actions, and add // "Download New Keyboard Schemes..." button into that menu if (schemesList) { foreach (QPushButton *button, dialog.findChildren()) { if (button->text() == i18n("More Actions")) { QMenu *moreActionsMenu = button->menu(); moreActionsMenu->addAction(i18n("Download New Keyboard Schemes..."), this, [this, schemesList]{slotGetNewKeyboardStuff(schemesList);}); break; } } } else { qWarning() << "Could not get list of schemes. Downloading new schemes is not available."; } dialog.addCollection(actionCollection(), i18nc("general keyboard shortcuts", "General")); dialog.configure(); } void MainWindow::slotPreferences(int page, int option) { /* * An instance of your dialog could be already created and could be * cached, in which case you want to display the cached dialog * instead of creating another one */ if (KConfigDialog::showDialog(QStringLiteral("settings"))) { KdenliveSettingsDialog *d = static_cast(KConfigDialog::exists(QStringLiteral("settings"))); if (page != -1) { d->showPage(page, option); } return; } // KConfigDialog didn't find an instance of this dialog, so lets // create it : // Get the mappable actions in localized form QMap actions; KActionCollection *collection = actionCollection(); QRegExp ampEx("&{1,1}"); for (const QString &action_name : m_actionNames) { QString action_text = collection->action(action_name)->text(); action_text.remove(ampEx); actions[action_text] = action_name; } KdenliveSettingsDialog *dialog = new KdenliveSettingsDialog(actions, m_gpuAllowed, this); connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::updateConfiguration); connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::configurationChanged); connect(dialog, &KdenliveSettingsDialog::doResetProfile, pCore->projectManager(), &ProjectManager::slotResetProfiles); connect(dialog, &KdenliveSettingsDialog::doResetConsumer, pCore->projectManager(), &ProjectManager::slotResetConsumers); connect(dialog, &KdenliveSettingsDialog::checkTabPosition, this, &MainWindow::slotCheckTabPosition); connect(dialog, &KdenliveSettingsDialog::restartKdenlive, this, &MainWindow::slotRestart); connect(dialog, &KdenliveSettingsDialog::updateLibraryFolder, pCore.get(), &Core::updateLibraryPath); connect(dialog, &KdenliveSettingsDialog::audioThumbFormatChanged, m_timelineTabs, &TimelineTabs::audioThumbFormatChanged); connect(dialog, &KdenliveSettingsDialog::resetView, this, &MainWindow::resetTimelineTracks); dialog->show(); if (page != -1) { dialog->showPage(page, option); } } void MainWindow::slotCheckTabPosition() { int pos = tabPosition(Qt::LeftDockWidgetArea); if (KdenliveSettings::tabposition() != pos) { setTabPosition(Qt::AllDockWidgetAreas, (QTabWidget::TabPosition)KdenliveSettings::tabposition()); } } void MainWindow::slotRestart() { m_exitCode = EXIT_RESTART; QApplication::closeAllWindows(); } void MainWindow::closeEvent(QCloseEvent *event) { KXmlGuiWindow::closeEvent(event); if (event->isAccepted()) { QApplication::exit(m_exitCode); return; } } void MainWindow::updateConfiguration() { // TODO: we should apply settings to all projects, not only the current one m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); slotSwitchAutomaticTransition(); // Update list of transcoding profiles buildDynamicActions(); loadClipActions(); } void MainWindow::slotSwitchVideoThumbs() { KdenliveSettings::setVideothumbnails(!KdenliveSettings::videothumbnails()); m_timelineTabs->showThumbnailsChanged(); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); } void MainWindow::slotSwitchAudioThumbs() { KdenliveSettings::setAudiothumbnails(!KdenliveSettings::audiothumbnails()); m_timelineTabs->showAudioThumbnailsChanged(); m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); } void MainWindow::slotSwitchMarkersComments() { KdenliveSettings::setShowmarkers(!KdenliveSettings::showmarkers()); getMainTimeline()->controller()->showMarkersChanged(); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); } void MainWindow::slotSwitchSnap() { KdenliveSettings::setSnaptopoints(!KdenliveSettings::snaptopoints()); m_buttonSnap->setChecked(KdenliveSettings::snaptopoints()); getMainTimeline()->controller()->snapChanged(KdenliveSettings::snaptopoints()); } void MainWindow::slotSwitchAutomaticTransition() { KdenliveSettings::setAutomatictransitions(!KdenliveSettings::automatictransitions()); m_buttonAutomaticTransition->setChecked(KdenliveSettings::automatictransitions()); } void MainWindow::slotDeleteItem() { if ((QApplication::focusWidget() != nullptr) && (QApplication::focusWidget()->parentWidget() != nullptr) && QApplication::focusWidget()->parentWidget() == pCore->bin()) { pCore->bin()->slotDeleteClip(); } else { QWidget *widget = QApplication::focusWidget(); while ((widget != nullptr) && widget != this) { if (widget == m_effectStackDock) { // TODO refac: reimplement // m_effectStack->deleteCurrentEffect(); return; } widget = widget->parentWidget(); } // effect stack has no focus getMainTimeline()->controller()->deleteSelectedClips(); } } void MainWindow::slotAddClipMarker() { std::shared_ptr clip(nullptr); GenTime pos; if (m_projectMonitor->isActive()) { return; } else { clip = m_clipMonitor->currentController(); pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps()); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage); return; } QString id = clip->AbstractProjectItem::clipId(); clip->getMarkerModel()->editMarkerGui(pos, this, true, clip.get()); } void MainWindow::slotDeleteClipMarker(bool allowGuideDeletion) { std::shared_ptr clip(nullptr); GenTime pos; if (m_projectMonitor->isActive()) { // TODO refac retrieve active clip /* if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = (GenTime(m_projectMonitor->position(), pCore->getCurrentFps()) - item->startPos() + item->cropStart()) / item->speed(); clip = pCore->bin()->getBinClip(item->getBinId()); } } */ } else { clip = m_clipMonitor->currentController(); pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps()); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } QString id = clip->AbstractProjectItem::clipId(); bool markerFound = false; CommentedTime marker = clip->getMarkerModel()->getMarker(pos, &markerFound); if (!markerFound) { if (allowGuideDeletion && m_projectMonitor->isActive()) { slotDeleteGuide(); } else { m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage); } return; } clip->getMarkerModel()->removeMarker(pos); } void MainWindow::slotDeleteAllClipMarkers() { std::shared_ptr clip(nullptr); if (m_projectMonitor->isActive()) { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { clip = pCore->bin()->getBinClip(item->getBinId()); } } */ } else { clip = m_clipMonitor->currentController(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } bool ok = clip->getMarkerModel()->removeAllMarkers(); if (!ok) { m_messageLabel->setMessage(i18n("An error occured while deleting markers"), ErrorMessage); return; } } void MainWindow::slotEditClipMarker() { std::shared_ptr clip(nullptr); GenTime pos; if (m_projectMonitor->isActive()) { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = (GenTime(m_projectMonitor->position(), pCore->getCurrentFps()) - item->startPos() + item->cropStart()) / item->speed(); clip = pCore->bin()->getBinClip(item->getBinId()); } } */ } else { clip = m_clipMonitor->currentController(); pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps()); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to edit marker"), ErrorMessage); return; } QString id = clip->AbstractProjectItem::clipId(); bool markerFound = false; CommentedTime oldMarker = clip->getMarkerModel()->getMarker(pos, &markerFound); if (!markerFound) { m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage); return; } clip->getMarkerModel()->editMarkerGui(pos, this, false, clip.get()); } void MainWindow::slotAddMarkerGuideQuickly() { if (!getMainTimeline() || !pCore->currentDoc()) { return; } if (m_clipMonitor->isActive()) { std::shared_ptr clip(m_clipMonitor->currentController()); GenTime pos(m_clipMonitor->position(), pCore->getCurrentFps()); if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage); return; } CommentedTime marker(pos, pCore->currentDoc()->timecode().getDisplayTimecode(pos, false), KdenliveSettings::default_marker_type()); clip->getMarkerModel()->addMarker(marker.time(), marker.comment(), marker.markerType()); } else { getMainTimeline()->controller()->switchGuide(); } } void MainWindow::slotAddGuide() { getMainTimeline()->controller()->switchGuide(); } void MainWindow::slotInsertSpace() { getMainTimeline()->controller()->insertSpace(); } void MainWindow::slotRemoveSpace() { getMainTimeline()->controller()->removeSpace(-1, -1, false); } void MainWindow::slotRemoveAllSpace() { getMainTimeline()->controller()->removeSpace(-1, -1, true); } void MainWindow::slotInsertTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); getMainTimeline()->controller()->addTrack(-1); } void MainWindow::slotDeleteTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); getMainTimeline()->controller()->deleteTrack(-1); } void MainWindow::slotSelectTrack() { getMainTimeline()->controller()->selectCurrentTrack(); } void MainWindow::slotSelectAllTracks() { getMainTimeline()->controller()->selectAll(); } void MainWindow::slotUnselectAllTracks() { getMainTimeline()->controller()->clearSelection(); } void MainWindow::slotEditGuide() { getMainTimeline()->controller()->editGuide(); } void MainWindow::slotDeleteGuide() { getMainTimeline()->controller()->switchGuide(-1, true); } void MainWindow::slotDeleteAllGuides() { pCore->currentDoc()->getGuideModel()->removeAllMarkers(); } void MainWindow::slotCutTimelineClip() { getMainTimeline()->controller()->cutClipUnderCursor(); } void MainWindow::slotInsertClipOverwrite() { const QString &binId = m_clipMonitor->activeClipId(); if (binId.isEmpty()) { // No clip in monitor return; } int pos = getMainTimeline()->controller()->insertZone(binId, m_clipMonitor->getZoneInfo(), true); if (pos > 0) { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); m_projectMonitor->refreshMonitorIfActive(true); getCurrentTimeline()->controller()->setPosition(pos); pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor); } } void MainWindow::slotInsertClipInsert() { const QString &binId = m_clipMonitor->activeClipId(); if (binId.isEmpty()) { // No clip in monitor return; } int pos = getMainTimeline()->controller()->insertZone(binId, m_clipMonitor->getZoneInfo(), false); if (pos > 0) { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); m_projectMonitor->refreshMonitorIfActive(true); getCurrentTimeline()->controller()->setPosition(pos); pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor); } } void MainWindow::slotExtractZone() { getMainTimeline()->controller()->extractZone(m_clipMonitor->getZoneInfo()); } void MainWindow::slotLiftZone() { getMainTimeline()->controller()->extractZone(m_clipMonitor->getZoneInfo(), true); } void MainWindow::slotPreviewRender() { if (pCore->currentDoc()) { getCurrentTimeline()->controller()->startPreviewRender(); } } void MainWindow::slotStopPreviewRender() { if (pCore->currentDoc()) { getCurrentTimeline()->controller()->stopPreviewRender(); } } void MainWindow::slotDefinePreviewRender() { if (pCore->currentDoc()) { getCurrentTimeline()->controller()->addPreviewRange(true); } } void MainWindow::slotRemovePreviewRender() { if (pCore->currentDoc()) { getCurrentTimeline()->controller()->addPreviewRange(false); } } void MainWindow::slotClearPreviewRender() { if (pCore->currentDoc()) { getCurrentTimeline()->controller()->clearPreviewRange(); } } void MainWindow::slotSelectTimelineClip() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineClip, true); } void MainWindow::slotSelectTimelineTransition() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineComposition, true); } void MainWindow::slotDeselectTimelineClip() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineClip, false); } void MainWindow::slotDeselectTimelineTransition() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineComposition, false); } void MainWindow::slotSelectAddTimelineClip() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineClip, true, true); } void MainWindow::slotSelectAddTimelineTransition() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineComposition, true, true); } void MainWindow::slotGroupClips() { getCurrentTimeline()->controller()->groupSelection(); } void MainWindow::slotUnGroupClips() { getCurrentTimeline()->controller()->unGroupSelection(); } void MainWindow::slotEditItemDuration() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->editItemDuration(); } */ } void MainWindow::slotAddProjectClip(const QUrl &url, const QStringList &folderInfo) { pCore->bin()->droppedUrls(QList() << url, folderInfo); } void MainWindow::slotAddProjectClipList(const QList &urls) { pCore->bin()->droppedUrls(urls); } void MainWindow::slotAddTransition(QAction *result) { if (!result) { return; } // TODO refac /* QStringList info = result->data().toStringList(); if (info.isEmpty() || info.count() < 2) { return; } QDomElement transition = transitions.getEffectByTag(info.at(0), info.at(1)); if (pCore->projectManager()->currentTimeline() && !transition.isNull()) { pCore->projectManager()->currentTimeline()->projectView()->slotAddTransitionToSelectedClips(transition.cloneNode().toElement()); } */ } void MainWindow::slotAddEffect(QAction *result) { qDebug()<<"// EFFECTS MENU TRIGGERED: "<data().toString(); if (!result) { return; } QString effectId = result->data().toString(); addEffect(effectId); } void MainWindow::addEffect(const QString &effectId) { if (m_assetPanel->effectStackOwner().first == ObjectType::TimelineClip) { // Add effect to the current timeline selection QVariantMap effectData; effectData.insert(QStringLiteral("kdenlive/effect"), effectId); pCore->window()->getMainTimeline()->controller()->addAsset(effectData); } else if (m_assetPanel->effectStackOwner().first == ObjectType::TimelineTrack || m_assetPanel->effectStackOwner().first == ObjectType::BinClip) { if (!m_assetPanel->addEffect(effectId)) { pCore->displayMessage(i18n("Cannot add effect to clip"), InformationMessage); } } else { pCore->displayMessage(i18n("Select an item to add effect"), InformationMessage); } } void MainWindow::slotZoomIn(bool zoomOnMouse) { slotSetZoom(m_zoomSlider->value() - 1, zoomOnMouse); slotShowZoomSliderToolTip(); } void MainWindow::slotZoomOut(bool zoomOnMouse) { slotSetZoom(m_zoomSlider->value() + 1, zoomOnMouse); slotShowZoomSliderToolTip(); } void MainWindow::slotFitZoom() { /* if (pCore->projectManager()->currentTimeline()) { m_zoomSlider->setValue(pCore->projectManager()->currentTimeline()->fitZoom()); // Make sure to reset scroll bar to start pCore->projectManager()->currentTimeline()->projectView()->scrollToStart(); } */ } void MainWindow::slotSetZoom(int value, bool zoomOnMouse) { value = qBound(m_zoomSlider->minimum(), value, m_zoomSlider->maximum()); m_timelineTabs->changeZoom(value, zoomOnMouse); updateZoomSlider(value); } void MainWindow::updateZoomSlider(int value) { slotUpdateZoomSliderToolTip(value); KdenliveDoc *project = pCore->currentDoc(); if (project) { project->setZoom(value); } m_zoomOut->setEnabled(value < m_zoomSlider->maximum()); m_zoomIn->setEnabled(value > m_zoomSlider->minimum()); QSignalBlocker blocker(m_zoomSlider); m_zoomSlider->setValue(value); } void MainWindow::slotShowZoomSliderToolTip(int zoomlevel) { if (zoomlevel != -1) { slotUpdateZoomSliderToolTip(zoomlevel); } QPoint global = m_zoomSlider->rect().topLeft(); global.ry() += m_zoomSlider->height() / 2; QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), m_zoomSlider->mapToGlobal(global)); QApplication::sendEvent(m_zoomSlider, &toolTipEvent); } void MainWindow::slotUpdateZoomSliderToolTip(int zoomlevel) { int max = m_zoomSlider->maximum() + 1; m_zoomSlider->setToolTip(i18n("Zoom Level: %1/%2", max - zoomlevel, max)); } void MainWindow::slotGotProgressInfo(const QString &message, int progress, MessageType type) { m_messageLabel->setProgressMessage(message, progress, type); } void MainWindow::customEvent(QEvent *e) { if (e->type() == QEvent::User) { m_messageLabel->setMessage(static_cast(e)->message(), MltError); } } void MainWindow::slotSnapRewind() { if (m_projectMonitor->isActive()) { getMainTimeline()->controller()->gotoPreviousSnap(); } else { m_clipMonitor->slotSeekToPreviousSnap(); } } void MainWindow::slotSnapForward() { if (m_projectMonitor->isActive()) { getMainTimeline()->controller()->gotoNextSnap(); } else { m_clipMonitor->slotSeekToNextSnap(); } } void MainWindow::slotClipStart() { if (m_projectMonitor->isActive()) { getMainTimeline()->controller()->seekCurrentClip(false); } else { m_clipMonitor->slotStart(); } } void MainWindow::slotClipEnd() { if (m_projectMonitor->isActive()) { getMainTimeline()->controller()->seekCurrentClip(true); } else { m_clipMonitor->slotEnd(); } } void MainWindow::slotChangeTool(QAction *action) { if (action == m_buttonSelectTool) { slotSetTool(SelectTool); } else if (action == m_buttonRazorTool) { slotSetTool(RazorTool); } else if (action == m_buttonSpacerTool) { slotSetTool(SpacerTool); } } void MainWindow::slotChangeEdit(QAction *action) { TimelineMode::EditMode mode = TimelineMode::NormalEdit; if (action == m_overwriteEditTool) { mode = TimelineMode::OverwriteEdit; } else if (action == m_insertEditTool) { mode = TimelineMode::InsertEdit; } getMainTimeline()->controller()->getModel()->setEditMode(mode); } void MainWindow::slotSetTool(ProjectTool tool) { if (pCore->currentDoc()) { // pCore->currentDoc()->setTool(tool); QString message; switch (tool) { case SpacerTool: message = i18n("Ctrl + click to use spacer on current track only"); break; case RazorTool: message = i18n("Click on a clip to cut it, Shift + move to preview cut frame"); break; default: message = i18n("Shift + click to create a selection rectangle, Ctrl + click to add an item to selection"); break; } m_messageLabel->setMessage(message, InformationMessage); getMainTimeline()->setTool(tool); } } void MainWindow::slotCopy() { getMainTimeline()->controller()->copyItem(); } void MainWindow::slotPaste() { getMainTimeline()->controller()->pasteItem(); } void MainWindow::slotPasteEffects() { getMainTimeline()->controller()->pasteEffects(); } void MainWindow::slotClipInTimeline(const QString &clipId, QList ids) { Q_UNUSED(clipId) QMenu *inTimelineMenu = static_cast(factory()->container(QStringLiteral("clip_in_timeline"), this)); QList actionList; for (int i = 0; i < ids.count(); ++i) { QString track = getMainTimeline()->controller()->getTrackNameFromIndex(pCore->getItemTrack(ObjectId(ObjectType::TimelineClip, ids.at(i)))); QString start = pCore->currentDoc()->timecode().getTimecodeFromFrames(pCore->getItemPosition(ObjectId(ObjectType::TimelineClip, ids.at(i)))); int j = 0; QAction *a = new QAction(track + QStringLiteral(": ") + start, inTimelineMenu); a->setData(ids.at(i)); connect(a, &QAction::triggered, this, &MainWindow::slotSelectClipInTimeline); while (j < actionList.count()) { if (actionList.at(j)->text() > a->text()) { break; } j++; } actionList.insert(j, a); } QList list = inTimelineMenu->actions(); unplugActionList(QStringLiteral("timeline_occurences")); qDeleteAll(list); plugActionList(QStringLiteral("timeline_occurences"), actionList); if (actionList.isEmpty()) { inTimelineMenu->setEnabled(false); } else { inTimelineMenu->setEnabled(true); } } void MainWindow::slotClipInProjectTree() { QList ids = getMainTimeline()->controller()->selection(); if (!ids.isEmpty()) { m_projectBinDock->raise(); ObjectId id(ObjectType::TimelineClip, ids.constFirst()); int start = pCore->getItemIn(id); int duration = pCore->getItemDuration(id); QPoint zone(start, start + duration); qDebug() << " - - selecting clip on monitor, zone: " << zone; if (m_projectMonitor->isActive()) { slotSwitchMonitors(); } int pos = m_projectMonitor->position(); int itemPos = pCore->getItemPosition(id); if (pos >= itemPos && pos < itemPos + duration) { pos -= (itemPos - start); } else { pos = -1; } pCore->selectBinClip(getMainTimeline()->controller()->getClipBinId(ids.constFirst()), pos, zone); } } void MainWindow::slotSelectClipInTimeline() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); QAction *action = qobject_cast(sender()); int clipId = action->data().toInt(); getMainTimeline()->controller()->focusItem(clipId); } /** Gets called when the window gets hidden */ void MainWindow::hideEvent(QHideEvent * /*event*/) { if (isMinimized() && pCore->monitorManager()) { pCore->monitorManager()->pauseActiveMonitor(); } } /*void MainWindow::slotSaveZone(Render *render, const QPoint &zone, DocClipBase *baseClip, QUrl path) { QPointer dialog = new QDialog(this); dialog->setWindowTitle("Save clip zone"); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QVBoxLayout *mainLayout = new QVBoxLayout; dialog->setLayout(mainLayout); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dialog->connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); dialog->connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); QLabel *label1 = new QLabel(i18n("Save clip zone as:"), this); if (path.isEmpty()) { QString tmppath = pCore->currentDoc()->projectFolder().path() + QDir::separator(); if (baseClip == nullptr) { tmppath.append("untitled.mlt"); } else { tmppath.append((baseClip->name().isEmpty() ? baseClip->fileURL().fileName() : baseClip->name()) + '-' + QString::number(zone.x()).rightJustified(4, '0') + QStringLiteral(".mlt")); } path = QUrl(tmppath); } KUrlRequester *url = new KUrlRequester(path, this); url->setFilter("video/mlt-playlist"); QLabel *label2 = new QLabel(i18n("Description:"), this); QLineEdit *edit = new QLineEdit(this); mainLayout->addWidget(label1); mainLayout->addWidget(url); mainLayout->addWidget(label2); mainLayout->addWidget(edit); mainLayout->addWidget(buttonBox); if (dialog->exec() == QDialog::Accepted) { if (QFile::exists(url->url().path())) { if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", url->url().path())) == KMessageBox::No) { slotSaveZone(render, zone, baseClip, url->url()); delete dialog; return; } } if (baseClip && !baseClip->fileURL().isEmpty()) { // create zone from clip url, so that we don't have problems with proxy clips QProcess p; QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.remove("MLT_PROFILE"); p.setProcessEnvironment(env); p.start(KdenliveSettings::rendererpath(), QStringList() << baseClip->fileURL().toLocalFile() << "in=" + QString::number(zone.x()) << "out=" + QString::number(zone.y()) << "-consumer" << "xml:" + url->url().path()); if (!p.waitForStarted(3000)) { KMessageBox::sorry(this, i18n("Cannot start MLT's renderer:\n%1", KdenliveSettings::rendererpath())); } else if (!p.waitForFinished(5000)) { KMessageBox::sorry(this, i18n("Timeout while creating xml output")); } } else render->saveZone(url->url(), edit->text(), zone); } delete dialog; }*/ void MainWindow::slotResizeItemStart() { getMainTimeline()->controller()->setInPoint(); } void MainWindow::slotResizeItemEnd() { getMainTimeline()->controller()->setOutPoint(); } int MainWindow::getNewStuff(const QString &configFile) { KNS3::Entry::List entries; QPointer dialog = new KNS3::DownloadDialog(configFile); if (dialog->exec() != 0) { entries = dialog->changedEntries(); } for (const KNS3::Entry &entry : entries) { if (entry.status() == KNS3::Entry::Installed) { qCDebug(KDENLIVE_LOG) << "// Installed files: " << entry.installedFiles(); } } delete dialog; return entries.size(); } void MainWindow::slotGetNewKeyboardStuff(QComboBox *schemesList) { if (getNewStuff(QStringLiteral(":data/kdenlive_keyboardschemes.knsrc")) > 0) { // Refresh keyboard schemes list (schemes list creation code copied from KShortcutSchemesEditor) QStringList schemes; schemes << QStringLiteral("Default"); // List files in the shortcuts subdir, each one is a scheme. See KShortcutSchemesHelper::{shortcutSchemeFileName,exportActionCollection} const QStringList shortcutsDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QCoreApplication::applicationName() + QStringLiteral("/shortcuts"), QStandardPaths::LocateDirectory); qCDebug(KDENLIVE_LOG) << "shortcut scheme dirs:" << shortcutsDirs; Q_FOREACH (const QString &dir, shortcutsDirs) { Q_FOREACH (const QString &file, QDir(dir).entryList(QDir::Files | QDir::NoDotAndDotDot)) { qCDebug(KDENLIVE_LOG) << "shortcut scheme file:" << file; schemes << file; } } schemesList->clear(); schemesList->addItems(schemes); } } void MainWindow::slotAutoTransition() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->autoTransition(); } */ } void MainWindow::slotSplitAV() { getMainTimeline()->controller()->splitAV(); } void MainWindow::slotSetAudioAlignReference() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->setAudioAlignReference(); } */ } void MainWindow::slotAlignAudio() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->alignAudio(); } */ } void MainWindow::slotUpdateClipType(QAction *action) { Q_UNUSED(action) // TODO refac /* if (pCore->projectManager()->currentTimeline()) { PlaylistState::ClipState state = (PlaylistState::ClipState)action->data().toInt(); pCore->projectManager()->currentTimeline()->projectView()->setClipType(state); } */ } void MainWindow::slotUpdateTimelineView(QAction *action) { int viewMode = action->data().toInt(); KdenliveSettings::setAudiotracksbelow(viewMode == 1); getMainTimeline()->controller()->getModel()->_resetView(); } void MainWindow::slotDvdWizard(const QString &url) { // We must stop the monitors since we create a new on in the dvd wizard QPointer w = new DvdWizard(pCore->monitorManager(), url, this); w->exec(); delete w; pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor); } void MainWindow::slotShowTimeline(bool show) { if (!show) { m_timelineState = saveState(); centralWidget()->setHidden(true); } else { centralWidget()->setHidden(false); restoreState(m_timelineState); } } void MainWindow::loadClipActions() { unplugActionList(QStringLiteral("add_effect")); plugActionList(QStringLiteral("add_effect"), m_effectsMenu->actions()); QList clipJobActions = getExtraActions(QStringLiteral("clipjobs")); unplugActionList(QStringLiteral("clip_jobs")); plugActionList(QStringLiteral("clip_jobs"), clipJobActions); QList atcActions = getExtraActions(QStringLiteral("audiotranscoderslist")); unplugActionList(QStringLiteral("audio_transcoders_list")); plugActionList(QStringLiteral("audio_transcoders_list"), atcActions); QList tcActions = getExtraActions(QStringLiteral("transcoderslist")); unplugActionList(QStringLiteral("transcoders_list")); plugActionList(QStringLiteral("transcoders_list"), tcActions); } void MainWindow::loadDockActions() { QList list = kdenliveCategoryMap.value(QStringLiteral("interface"))->actions(); // Sort actions QMap sorted; QStringList sortedList; for (QAction *a : list) { sorted.insert(a->text(), a); sortedList << a->text(); } QList orderedList; sortedList.sort(Qt::CaseInsensitive); for (const QString &text : sortedList) { orderedList << sorted.value(text); } unplugActionList(QStringLiteral("dock_actions")); plugActionList(QStringLiteral("dock_actions"), orderedList); } void MainWindow::buildDynamicActions() { KActionCategory *ts = nullptr; if (kdenliveCategoryMap.contains(QStringLiteral("clipjobs"))) { ts = kdenliveCategoryMap.take(QStringLiteral("clipjobs")); delete ts; } ts = new KActionCategory(i18n("Clip Jobs"), m_extraFactory->actionCollection()); Mlt::Profile profile; std::unique_ptr filter; for (const QString &stab : {QStringLiteral("vidstab"), QStringLiteral("videostab2"), QStringLiteral("videostab")}) { filter.reset(new Mlt::Filter(profile, stab.toUtf8().constData())); if ((filter != nullptr) && filter->is_valid()) { QAction *action = new QAction(i18n("Stabilize") + QStringLiteral(" (") + stab + QLatin1Char(')'), m_extraFactory->actionCollection()); ts->addAction(action->text(), action); connect(action, &QAction::triggered, [this, stab]() { pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(), {}, i18np("Stabilize clip", "Stabilize clips", pCore->bin()->selectedClipsIds().size()), stab); }); break; } } filter.reset(new Mlt::Filter(profile, "motion_est")); if (filter) { if (filter->is_valid()) { QAction *action = new QAction(i18n("Automatic scene split"), m_extraFactory->actionCollection()); ts->addAction(action->text(), action); connect(action, &QAction::triggered, [&]() { pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(), {}, i18n("Scene detection")); }); } } if (true /* TODO: check if timewarp producer is available */) { QAction *action = new QAction(i18n("Duplicate clip with speed change"), m_extraFactory->actionCollection()); ts->addAction(action->text(), action); connect(action, &QAction::triggered, [&]() { pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(), {}, i18n("Change clip speed")); }); } // TODO refac reimplement analyseclipjob /* QAction *action = new QAction(i18n("Analyse keyframes"), m_extraFactory->actionCollection()); QStringList stabJob(QString::number((int)AbstractClipJob::ANALYSECLIPJOB)); action->setData(stabJob); ts->addAction(action->text(), action); connect(action, &QAction::triggered, pCore->bin(), &Bin::slotStartClipJob); */ kdenliveCategoryMap.insert(QStringLiteral("clipjobs"), ts); if (kdenliveCategoryMap.contains(QStringLiteral("transcoderslist"))) { ts = kdenliveCategoryMap.take(QStringLiteral("transcoderslist")); delete ts; } if (kdenliveCategoryMap.contains(QStringLiteral("audiotranscoderslist"))) { ts = kdenliveCategoryMap.take(QStringLiteral("audiotranscoderslist")); delete ts; } // TODO refac : reimplement transcode /* ts = new KActionCategory(i18n("Transcoders"), m_extraFactory->actionCollection()); KActionCategory *ats = new KActionCategory(i18n("Extract Audio"), m_extraFactory->actionCollection()); KSharedConfigPtr config = KSharedConfig::openConfig(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdenlivetranscodingrc")), KConfig::CascadeConfig); KConfigGroup transConfig(config, "Transcoding"); // read the entries QMap profiles = transConfig.entryMap(); QMapIterator i(profiles); while (i.hasNext()) { i.next(); QStringList transList; transList << QString::number((int)AbstractClipJob::TRANSCODEJOB); transList << i.value().split(QLatin1Char(';')); auto *a = new QAction(i.key(), m_extraFactory->actionCollection()); a->setData(transList); if (transList.count() > 1) { a->setToolTip(transList.at(1)); } // slottranscode connect(a, &QAction::triggered, pCore->bin(), &Bin::slotStartClipJob); if (transList.count() > 3 && transList.at(3) == QLatin1String("audio")) { // This is an audio transcoding action ats->addAction(i.key(), a); } else { ts->addAction(i.key(), a); } } kdenliveCategoryMap.insert(QStringLiteral("transcoderslist"), ts); kdenliveCategoryMap.insert(QStringLiteral("audiotranscoderslist"), ats); */ // Populate View menu with show / hide actions for dock widgets KActionCategory *guiActions = nullptr; if (kdenliveCategoryMap.contains(QStringLiteral("interface"))) { guiActions = kdenliveCategoryMap.take(QStringLiteral("interface")); delete guiActions; } guiActions = new KActionCategory(i18n("Interface"), actionCollection()); QAction *showTimeline = new QAction(i18n("Timeline"), this); showTimeline->setCheckable(true); showTimeline->setChecked(true); connect(showTimeline, &QAction::triggered, this, &MainWindow::slotShowTimeline); guiActions->addAction(showTimeline->text(), showTimeline); actionCollection()->addAction(showTimeline->text(), showTimeline); QList docks = findChildren(); for (int j = 0; j < docks.count(); ++j) { QDockWidget *dock = docks.at(j); QAction *dockInformations = dock->toggleViewAction(); if (!dockInformations) { continue; } dockInformations->setChecked(!dock->isHidden()); guiActions->addAction(dockInformations->text(), dockInformations); } kdenliveCategoryMap.insert(QStringLiteral("interface"), guiActions); } QList MainWindow::getExtraActions(const QString &name) { if (!kdenliveCategoryMap.contains(name)) { return QList(); } return kdenliveCategoryMap.value(name)->actions(); } void MainWindow::slotTranscode(const QStringList &urls) { Q_UNUSED(urls) // TODO refac : remove or reimplement transcoding /* QString params; QString desc; if (urls.isEmpty()) { QAction *action = qobject_cast(sender()); QStringList transList = action->data().toStringList(); pCore->bin()->startClipJob(transList); return; } if (urls.isEmpty()) { m_messageLabel->setMessage(i18n("No clip to transcode"), ErrorMessage); return; } qCDebug(KDENLIVE_LOG) << "// TRANSODING FOLDER: " << pCore->bin()->getFolderInfo(); ClipTranscode *d = new ClipTranscode(urls, params, QStringList(), desc, pCore->bin()->getFolderInfo()); connect(d, &ClipTranscode::addClip, this, &MainWindow::slotAddProjectClip); d->show(); */ } void MainWindow::slotTranscodeClip() { // TODO refac : remove or reimplement transcoding /* QString allExtensions = ClipCreationDialog::getExtensions().join(QLatin1Char(' ')); const QString dialogFilter = i18n("All Supported Files") + QLatin1Char('(') + allExtensions + QStringLiteral(");;") + i18n("All Files") + QStringLiteral("(*)"); QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); QStringList urls = QFileDialog::getOpenFileNames(this, i18n("Files to transcode"), clipFolder, dialogFilter); if (urls.isEmpty()) { return; } slotTranscode(urls); */ } void MainWindow::slotSetDocumentRenderProfile(const QMap &props) { KdenliveDoc *project = pCore->currentDoc(); bool modified = false; QMapIterator i(props); while (i.hasNext()) { i.next(); if (project->getDocumentProperty(i.key()) == i.value()) { continue; } project->setDocumentProperty(i.key(), i.value()); modified = true; } if (modified) { project->setModified(); } } void MainWindow::slotUpdateTimecodeFormat(int ix) { KdenliveSettings::setFrametimecode(ix == 1); m_clipMonitor->updateTimecodeFormat(); m_projectMonitor->updateTimecodeFormat(); // TODO refac: reimplement ? // m_effectStack->transitionConfig()->updateTimecodeFormat(); // m_effectStack->updateTimecodeFormat(); pCore->bin()->updateTimecodeFormat(); getMainTimeline()->controller()->frameFormatChanged(); m_timeFormatButton->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); } void MainWindow::slotRemoveFocus() { getMainTimeline()->setFocus(); } void MainWindow::slotShutdown() { pCore->currentDoc()->setModified(false); // Call shutdown QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface(); if ((interface != nullptr) && interface->isServiceRegistered(QStringLiteral("org.kde.ksmserver"))) { QDBusInterface smserver(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface")); smserver.call(QStringLiteral("logout"), 1, 2, 2); } else if ((interface != nullptr) && interface->isServiceRegistered(QStringLiteral("org.gnome.SessionManager"))) { QDBusInterface smserver(QStringLiteral("org.gnome.SessionManager"), QStringLiteral("/org/gnome/SessionManager"), QStringLiteral("org.gnome.SessionManager")); smserver.call(QStringLiteral("Shutdown")); } } void MainWindow::slotSwitchMonitors() { pCore->monitorManager()->slotSwitchMonitors(!m_clipMonitor->isActive()); if (m_projectMonitor->isActive()) { getMainTimeline()->setFocus(); } else { pCore->bin()->focusBinView(); } } void MainWindow::slotSwitchMonitorOverlay(QAction *action) { if (pCore->monitorManager()->isActive(Kdenlive::ClipMonitor)) { m_clipMonitor->switchMonitorInfo(action->data().toInt()); } else { m_projectMonitor->switchMonitorInfo(action->data().toInt()); } } void MainWindow::slotSwitchDropFrames(bool drop) { m_clipMonitor->switchDropFrames(drop); m_projectMonitor->switchDropFrames(drop); } void MainWindow::slotSetMonitorGamma(int gamma) { KdenliveSettings::setMonitor_gamma(gamma); m_clipMonitor->updateMonitorGamma(); m_projectMonitor->updateMonitorGamma(); } void MainWindow::slotInsertZoneToTree() { if (!m_clipMonitor->isActive() || m_clipMonitor->currentController() == nullptr) { return; } QPoint info = m_clipMonitor->getZoneInfo(); QString id; pCore->projectItemModel()->requestAddBinSubClip(id, info.x(), info.y(), QString(), m_clipMonitor->activeClipId()); } void MainWindow::slotInsertZoneToTimeline() { QPoint info = m_clipMonitor->getZoneInfo(); QString clipData = QString("%1#%2#%3").arg(m_clipMonitor->activeClipId()).arg(info.x()).arg(info.y()); int cid = getMainTimeline()->controller()->insertClip(-1, -1, clipData, true, true, true); if (cid == -1) { pCore->displayMessage(i18n("Cannot insert clip at requested position"), InformationMessage); } else { getMainTimeline()->controller()->seekToClip(cid, true); } } void MainWindow::slotMonitorRequestRenderFrame(bool request) { if (request) { m_projectMonitor->sendFrameForAnalysis(true); return; } for (int i = 0; i < m_gfxScopesList.count(); ++i) { if (m_gfxScopesList.at(i)->isVisible() && tabifiedDockWidgets(m_gfxScopesList.at(i)).isEmpty() && static_cast(m_gfxScopesList.at(i)->widget())->autoRefreshEnabled()) { request = true; break; } } #ifdef DEBUG_MAINW qCDebug(KDENLIVE_LOG) << "Any scope accepting new frames? " << request; #endif if (!request) { m_projectMonitor->sendFrameForAnalysis(false); } } void MainWindow::slotUpdateProxySettings() { KdenliveDoc *project = pCore->currentDoc(); if (m_renderWidget) { m_renderWidget->updateProxyConfig(project->useProxy()); } pCore->bin()->refreshProxySettings(); } void MainWindow::slotArchiveProject() { KdenliveDoc *doc = pCore->currentDoc(); QDomDocument xmlDoc = doc->xmlSceneList(m_projectMonitor->sceneList(doc->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile())); QPointer d( new ArchiveWidget(doc->url().fileName(), xmlDoc, getMainTimeline()->controller()->extractCompositionLumas(), this)); if (d->exec() != 0) { m_messageLabel->setMessage(i18n("Archiving project"), OperationCompletedMessage); } } void MainWindow::slotDownloadResources() { QString currentFolder; if (pCore->currentDoc()) { currentFolder = pCore->currentDoc()->projectDataFolder(); } else { currentFolder = KdenliveSettings::defaultprojectfolder(); } auto *d = new ResourceWidget(currentFolder); connect(d, &ResourceWidget::addClip, this, &MainWindow::slotAddProjectClip); d->show(); } void MainWindow::slotProcessImportKeyframes(GraphicsRectItem type, const QString &tag, const QString &keyframes) { Q_UNUSED(keyframes) Q_UNUSED(tag) if (type == AVWidget) { // This data should be sent to the effect stack // TODO REFAC reimplement // m_effectStack->setKeyframes(tag, data); } else if (type == TransitionWidget) { // This data should be sent to the transition stack // TODO REFAC reimplement // m_effectStack->transitionConfig()->setKeyframes(tag, data); } else { // Error } } void MainWindow::slotAlignPlayheadToMousePos() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); getMainTimeline()->controller()->seekToMouse(); } void MainWindow::triggerKey(QKeyEvent *ev) { // Hack: The QQuickWindow that displays fullscreen monitor does not integrate quith QActions. // so on keypress events we parse keys and check for shortcuts in all existing actions QKeySequence seq; // Remove the Num modifier or some shortcuts like "*" will not work if (ev->modifiers() != Qt::KeypadModifier) { seq = QKeySequence(ev->key() + static_cast(ev->modifiers())); } else { seq = QKeySequence(ev->key()); } QList collections = KActionCollection::allCollections(); for (int i = 0; i < collections.count(); ++i) { KActionCollection *coll = collections.at(i); for (QAction *tempAction : coll->actions()) { if (tempAction->shortcuts().contains(seq)) { // Trigger action tempAction->trigger(); ev->accept(); return; } } } } QDockWidget *MainWindow::addDock(const QString &title, const QString &objectName, QWidget *widget, Qt::DockWidgetArea area) { QDockWidget *dockWidget = new QDockWidget(title, this); dockWidget->setObjectName(objectName); dockWidget->setWidget(widget); addDockWidget(area, dockWidget); connect(dockWidget, &QDockWidget::dockLocationChanged, this, [this](Qt::DockWidgetArea dockLocationArea) { if (dockLocationArea == Qt::NoDockWidgetArea) { updateDockTitleBars(false); } else { updateDockTitleBars(true); } }); connect(dockWidget, &QDockWidget::topLevelChanged, this, &MainWindow::updateDockTitleBars); return dockWidget; } void MainWindow::slotUpdateMonitorOverlays(int id, int code) { QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this)); if (!monitorOverlay) { return; } QList actions = monitorOverlay->actions(); for (QAction *ac : actions) { int mid = ac->data().toInt(); if (mid == 0x010) { ac->setEnabled(id == Kdenlive::ClipMonitor); } ac->setChecked(code & mid); } } void MainWindow::slotChangeStyle(QAction *a) { QString style = a->data().toString(); KdenliveSettings::setWidgetstyle(style); doChangeStyle(); } void MainWindow::doChangeStyle() { QString newStyle = KdenliveSettings::widgetstyle(); if (newStyle.isEmpty() || newStyle == QStringLiteral("Default")) { newStyle = defaultStyle("Breeze"); } QApplication::setStyle(QStyleFactory::create(newStyle)); } bool MainWindow::isTabbedWith(QDockWidget *widget, const QString &otherWidget) { QList tabbed = tabifiedDockWidgets(widget); for (int i = 0; i < tabbed.count(); i++) { if (tabbed.at(i)->objectName() == otherWidget) { return true; } } return false; } void MainWindow::updateDockTitleBars(bool isTopLevel) { if (!KdenliveSettings::showtitlebars() || !isTopLevel) { return; } QList docks = pCore->window()->findChildren(); for (int i = 0; i < docks.count(); ++i) { QDockWidget *dock = docks.at(i); QWidget *bar = dock->titleBarWidget(); if (dock->isFloating()) { if (bar) { dock->setTitleBarWidget(nullptr); delete bar; } continue; } QList docked = pCore->window()->tabifiedDockWidgets(dock); if (docked.isEmpty()) { if (bar) { dock->setTitleBarWidget(nullptr); delete bar; } continue; } bool hasVisibleDockSibling = false; for (QDockWidget *sub : docked) { if (sub->toggleViewAction()->isChecked()) { // we have another docked widget, so tabs are visible and can be used instead of title bars hasVisibleDockSibling = true; break; } } if (!hasVisibleDockSibling) { if (bar) { dock->setTitleBarWidget(nullptr); delete bar; } continue; } if (!bar) { dock->setTitleBarWidget(new QWidget); } } } void MainWindow::slotToggleAutoPreview(bool enable) { KdenliveSettings::setAutopreview(enable); if (enable && getMainTimeline()) { getMainTimeline()->controller()->startPreviewRender(); } } void MainWindow::configureToolbars() { // Since our timeline toolbar is a non-standard toolbar (as it is docked in a custom widget, not // in a QToolBarDockArea, we have to hack KXmlGuiWindow to avoid a crash when saving toolbar config. // This is why we hijack the configureToolbars() and temporarily move the toolbar to a standard location QVBoxLayout *ctnLay = (QVBoxLayout *)m_timelineToolBarContainer->layout(); ctnLay->removeWidget(m_timelineToolBar); addToolBar(Qt::BottomToolBarArea, m_timelineToolBar); auto *toolBarEditor = new KEditToolBar(guiFactory(), this); toolBarEditor->setAttribute(Qt::WA_DeleteOnClose); connect(toolBarEditor, SIGNAL(newToolBarConfig()), SLOT(saveNewToolbarConfig())); connect(toolBarEditor, &QDialog::finished, this, &MainWindow::rebuildTimlineToolBar); toolBarEditor->show(); } void MainWindow::rebuildTimlineToolBar() { // Timeline toolbar settings changed, we can now re-add our toolbar to custom location m_timelineToolBar = toolBar(QStringLiteral("timelineToolBar")); removeToolBar(m_timelineToolBar); m_timelineToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); QVBoxLayout *ctnLay = (QVBoxLayout *)m_timelineToolBarContainer->layout(); if (ctnLay) { ctnLay->insertWidget(0, m_timelineToolBar); } m_timelineToolBar->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_timelineToolBar, &QWidget::customContextMenuRequested, this, &MainWindow::showTimelineToolbarMenu); m_timelineToolBar->setVisible(true); } void MainWindow::showTimelineToolbarMenu(const QPoint &pos) { QMenu menu; menu.addAction(actionCollection()->action(KStandardAction::name(KStandardAction::ConfigureToolbars))); QMenu *contextSize = new QMenu(i18n("Icon Size")); menu.addMenu(contextSize); auto *sizeGroup = new QActionGroup(contextSize); int currentSize = m_timelineToolBar->iconSize().width(); QAction *a = new QAction(i18nc("@item:inmenu Icon size", "Default"), contextSize); a->setData(m_timelineToolBar->iconSizeDefault()); a->setCheckable(true); if (m_timelineToolBar->iconSizeDefault() == currentSize) { a->setChecked(true); } a->setActionGroup(sizeGroup); contextSize->addAction(a); KIconTheme *theme = KIconLoader::global()->theme(); QList avSizes; if (theme) { avSizes = theme->querySizes(KIconLoader::Toolbar); } qSort(avSizes); if (avSizes.count() < 10) { // Fixed or threshold type icons Q_FOREACH (int it, avSizes) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map auto *sizeAction = new QAction(text, contextSize); sizeAction->setData(it); sizeAction->setCheckable(true); sizeAction->setActionGroup(sizeGroup); if (it == currentSize) { sizeAction->setChecked(true); } contextSize->addAction(sizeAction); } } else { // Scalable icons. const int progression[] = {16, 22, 32, 48, 64, 96, 128, 192, 256}; for (uint i = 0; i < 9; i++) { Q_FOREACH (int it, avSizes) { if (it >= progression[i]) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map auto *sizeAction = new QAction(text, contextSize); sizeAction->setData(it); sizeAction->setCheckable(true); sizeAction->setActionGroup(sizeGroup); if (it == currentSize) { sizeAction->setChecked(true); } contextSize->addAction(sizeAction); break; } } } } connect(contextSize, &QMenu::triggered, this, &MainWindow::setTimelineToolbarIconSize); menu.exec(m_timelineToolBar->mapToGlobal(pos)); contextSize->deleteLater(); } void MainWindow::setTimelineToolbarIconSize(QAction *a) { if (!a) { return; } int size = a->data().toInt(); m_timelineToolBar->setIconDimensions(size); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup mainConfig(config, QStringLiteral("MainWindow")); KConfigGroup tbGroup(&mainConfig, QStringLiteral("Toolbar timelineToolBar")); m_timelineToolBar->saveSettings(tbGroup); } void MainWindow::slotManageCache() { QDialog d(this); d.setWindowTitle(i18n("Manage Cache Data")); auto *lay = new QVBoxLayout; TemporaryData tmp(pCore->currentDoc(), false, this); connect(&tmp, &TemporaryData::disableProxies, this, &MainWindow::slotDisableProxies); // TODO refac /* connect(&tmp, SIGNAL(disablePreview()), pCore->projectManager()->currentTimeline(), SLOT(invalidateRange())); */ QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject); lay->addWidget(&tmp); lay->addWidget(buttonBox); d.setLayout(lay); d.exec(); } void MainWindow::slotUpdateCompositing(QAction *compose) { int mode = compose->data().toInt(); getMainTimeline()->controller()->switchCompositing(mode); if (m_renderWidget) { m_renderWidget->errorMessage(RenderWidget::CompositeError, mode == 1 ? i18n("Rendering using low quality track compositing") : QString()); } } void MainWindow::slotUpdateCompositeAction(int mode) { QList actions = m_compositeAction->actions(); for (int i = 0; i < actions.count(); i++) { if (actions.at(i)->data().toInt() == mode) { m_compositeAction->setCurrentAction(actions.at(i)); break; } } if (m_renderWidget) { m_renderWidget->errorMessage(RenderWidget::CompositeError, mode == 1 ? i18n("Rendering using low quality track compositing") : QString()); } } void MainWindow::showMenuBar(bool show) { if (!show) { KMessageBox::information(this, i18n("This will hide the menu bar completely. You can show it again by typing Ctrl+M."), i18n("Hide menu bar"), QStringLiteral("show-menubar-warning")); } menuBar()->setVisible(show); } void MainWindow::forceIconSet(bool force) { KdenliveSettings::setForce_breeze(force); if (force) { // Check current color theme QColor background = qApp->palette().window().color(); bool useDarkIcons = background.value() < 100; KdenliveSettings::setUse_dark_breeze(useDarkIcons); } if (KMessageBox::warningContinueCancel(this, i18n("Kdenlive needs to be restarted to apply icon theme change. Restart now ?")) == KMessageBox::Continue) { slotRestart(); } } void MainWindow::slotSwitchTrimMode() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->switchTrimMode(); } */ } void MainWindow::setTrimMode(const QString &mode){ Q_UNUSED(mode) // TODO refac /* if (pCore->projectManager()->currentTimeline()) { m_trimLabel->setText(mode); m_trimLabel->setVisible(!mode.isEmpty()); } */ } TimelineWidget *MainWindow::getMainTimeline() const { return m_timelineTabs->getMainTimeline(); } TimelineWidget *MainWindow::getCurrentTimeline() const { return m_timelineTabs->getCurrentTimeline(); } void MainWindow::resetTimelineTracks() { TimelineWidget *current = getCurrentTimeline(); if (current) { current->controller()->resetTrackHeight(); } } void MainWindow::slotChangeSpeed(int speed) { ObjectId owner = m_assetPanel->effectStackOwner(); // TODO: manage bin clips / tracks if (owner.first == ObjectType::TimelineClip) { getCurrentTimeline()->controller()->changeItemSpeed(owner.second, speed); } } void MainWindow::slotSwitchTimelineZone(bool active) { pCore->currentDoc()->setDocumentProperty(QStringLiteral("enableTimelineZone"), active ? QStringLiteral("1") : QStringLiteral("0")); getCurrentTimeline()->controller()->useRulerChanged(); QSignalBlocker blocker(m_useTimelineZone); m_useTimelineZone->setActive(active); } void MainWindow::slotGrabItem() { getCurrentTimeline()->controller()->grabCurrent(); } // static void MainWindow::refreshLumas() { // Check for Kdenlive installed luma files, add empty string at start for no luma QStringList imagefiles; QStringList fileFilters; MainWindow::m_lumaFiles.clear(); fileFilters << QStringLiteral("*.png") << QStringLiteral("*.pgm"); QStringList customLumas = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("lumas"), QStandardPaths::LocateDirectory); customLumas.append(QString(mlt_environment("MLT_DATA")) + QStringLiteral("/lumas")); for (const QString &folder : customLumas) { QDir topDir(folder); QStringList folders = topDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); for (const QString &f : folders) { QDir dir(topDir.absoluteFilePath(f)); QStringList filesnames = dir.entryList(fileFilters, QDir::Files); if (MainWindow::m_lumaFiles.contains(f)) { imagefiles = MainWindow::m_lumaFiles.value(f); } for (const QString &fname : filesnames) { imagefiles.append(dir.absoluteFilePath(fname)); } MainWindow::m_lumaFiles.insert(f, imagefiles); } } } #ifdef DEBUG_MAINW #undef DEBUG_MAINW #endif